A number of collections, such as linked-lists, binary-trees, or B-Trees are most easily implemented with aliasing pointers.

Overview

StaticRc is a safe reference-counted pointer, similar to Rc or Arc, though performing its reference-counting at compile-time rather than run-time, and therefore avoiding any run-time overhead.

Motivating Example

A number of collections, such as linked-lists, binary-trees, or B-Trees are most easily implemented with aliasing pointers.

Traditionally, this requires either unsafe raw pointers, or using Rc or Arc depending on the scenario. A key observation, however, is that in those collections the exact number of aliases is known at compile-time:

  • A doubly linked-list has 2 pointers to each node.
  • A binary-tree has 3 pointers to each node: one from the parent, and one from each child.
  • A B-Tree of cardinality N has N+1 pointers to each node.

In this type of scenario, static-rc offers the safety of Rc and Arc, with the performance of unsafe raw pointers.

That's all folks!

And thanks for reading.

Comments
  • Lift internal assumption is false

    Lift internal assumption is false

    In lift.rs, in the implementation of the lift function, there is this line:

     debug_assert_ne!(slot as *const _, &root as *const _);
    

    slot, &root presumably can't point to the same location because root owns a value whereas slot is a mutable reference to a value (of the same type), and both of them being the same would violate Rust's aliasing guarantees.

    However, this is false in this example:

    fn lift_counterexample() {
        GhostToken::new(|mut token| {
            let cell = GhostCell::new(Box::new(7));
            lift_with_mut(cell, &mut token, |cell_ref, token_ref| {
                GhostCell::from_mut(cell_ref.borrow_mut(token_ref))
            });
        })
    }
    

    Here, a &GhostCell<T> is safely aliasing a &mut GhostCell<T>. This happens by the chain of &GhostCell<T> -> &mut T -> &mut GhostCell<T>, which can be called in regular code without using the lift function. To my understanding, this is safe because

    • GhostCell ensures these references cannot be used at the same time, by the use of the token.
    • GhostCell is/contains an UnsafeCell which causes some things to be allowable, both in terms of stacked borrows and in terms of rustc assuming less things.

    Therefore, the code of lift is wrong to assume that this can never happen.

    opened by noamtashma 5
  • Rc as rcref

    Rc as rcref

    This pull request adds a feature allowing StaticRc and StaticRcRef to be frozen in order to temporarily create a new StaticRcRef that has the same "ratio" of a &mut T. Since the original is frozen, the overall ownership of the &mut T is still preserved. This can allow changing a value through separate StaticRcs stored in different parts while not having to constantly move them around.

    Honestly I'm not sure how useful it is, because I haven't had time to check yet, but I have a feeling this can be used to improve some of the data structures that can currently be implemented using static_rc.

    I'm pretty sure this is sound, but this does "allow" more than 1 overall ownership at a single time. But it isn't supposed to matter since only 1 overall ownership is usable at any single time. Perhaps you would like to put this under an experimental feature for now?

    The pull request is currently missing tests, except the two doctests.

    opened by noamtashma 5
  • StaticRcRef::pin is unsound

    StaticRcRef::pin is unsound

    This method lets you create a pinned reference from any arbitrary &mut T, but that isn't legal - we can later drop the Pin<StaticRcRef> and regain access to the bare &mut T.

    For example we can write this function to poll any Future, even if it isn't pinned:

    fn poll<F: Future>(x: &mut F, cx: &mut Context<'_>) -> Poll<F::Output> {
        StaticRcRef::<_, 1, 1>::pin(x).as_mut().poll(cx)
    }
    

    This can trigger UB in safe code:

    use futures::task::noop_waker;
    use static_rc::rcref::StaticRcRef;
    use std::future::Future;
    use std::task::{Context, Poll};
    
    fn poll<F: Future>(x: &mut F, cx: &mut Context<'_>) -> Poll<F::Output> {
        StaticRcRef::<_, 1, 1>::pin(x).as_mut().poll(cx)
    }
    fn main() {
        let (tx, rx) = futures::channel::oneshot::channel();
        let mut future = Ok(async move {
            let s = "asdf".to_string();
            let s_ref = &s;
            dbg!(&s as *const _, s_ref as *const _, s_ref);
            rx.await.unwrap();
            dbg!(&s as *const _, s_ref as *const _, s_ref); // segmentation fault
        });
        let _ = poll(future.as_mut().unwrap(), &mut Context::from_waker(&noop_waker()));
        tx.send(()).unwrap();
        let mut future2 = std::mem::replace(&mut future, Err([1; 128])).unwrap();
        let _ = poll(&mut future2, &mut Context::from_waker(&noop_waker()));
    }
    
    opened by goffrie 3
  • I love this idea!!

    I love this idea!!

    this isn't really an issue per se more of a quick remark!!. this seems quite cool or amazing. it might make designing things like many types of linked lists in rust much safer than currently by removing a huge amount of unsafe even if a still must be used! it would be a vast improvement over the current status quo of total unsafe code!. so far I cant see a flaw in the reasoning that would make this code unsafe in anyway!! however, I do believe while this can get a lot closer to unsafe pointers performance there can be cases like doubly linked circular list where an enum to say separate the circular links with owner ship slight over with enum discriminant.

    i would love to read like a thesis paper on this design!!

    opened by warlord500 1
  • Add track-caller annotations and enforce that drop never leaks

    Add track-caller annotations and enforce that drop never leaks

    This PR adds #[track_caller] annotations to functions that can panic, and also changes the debug_assert_eq!(...) in the Drop implementation to assert_eq!(...). IMO, behavior as important as leaking should not change between debug and release profiles.

    opened by lachlansneff 1
  • fix: update nightly features

    fix: update nightly features

    This PR updates two nightly-only features to be compatible with the latest rust versions:

    • nightly-async-stream - stream got renamed to async_iter in 1.60. Renamed the crate feature accordingly
    • compile-time-ratio - With the const generics MVP landing in 1.51 the feature gate names have changed. Updated the acordingly and added a constraint on StaticRc::as_rcref that was causing compilation to fail.
    opened by JonasKruckenberg 0
  • Crate maintenance status

    Crate maintenance status

    Hey ✌🏻 I'm planning to use static-rc amd ghost-cell in a bigger project and would like to know if you're interested in polishing these crates beyond the "PoC" phase, i.e. add CI checks, proper releases etc. I would be more than happy to contribute these if you don't have the bandwidth!

    opened by JonasKruckenberg 1
  • Is it possible to provide something like `StaticRc<T, 0, DEN>` for temporarily sharing?

    Is it possible to provide something like `StaticRc` for temporarily sharing?

    Sometimes, it needs to yield a temporarily shared pointer of StaticRc. In Rc, it is done by Clone::clone, and the reference counting in Rc can make sure the shared objects outlives all of its shared pointers.

    But in StaticRc, in can only be achieved by StaticRc::split, which requires to consume the value first, and the splitted pointers must be joined back after usage.

    I am thinking of such matters:

    What if providing something like StaticRc<T, 0, DEN> or naming Weak<T> to allow temporarily sharing without consuming the pointer or borrowing from it?

    impl<T, const NUM: usize, const DEN: usize> StaticRc<T, NUM, DEN> {
        pub fn split(&self) -> StaticRc<0, DEN>
        where
            AssertLeType!(NUM + 1, DEN): Sized, //  here `NUM` must be `< DEN` because owned objects might be mutated
        {
            let pointer = self.pointer;
            StaticRc { pointer }
        }
    }
    

    However, it will never be safe if the temporarily yielded pointers lives longer than the host pointer. So there need to be a mechanism to constrain the lifetime of StaticRc.

    The only thing come up in my mind is the GhostCell with GhostToken. If we add a covariant lifetime tag 'id like StaticRc<'id, T, NUM, DEN>, then it is possible to require pub fn split(&self) -> StaticRc<'id, 0, DEN> where the yielded pointer lives no longer than the host pointer. Nevertheless, the implementation details can be much more complicated.


    Actually, I have been encountering this problem where using StaticRc and GhostCell to develop a safe doubly-linked list.

    The type of linked list is defined as following:

    pub struct List<'id, T> {
        head: Option<NodePtr<'id, T>>,
        tail: Option<NodePtr<'id, T>>,
        len: usize,
    }
    
    struct Node<'id, T> {
        next: Option<NodePtr<'id, T>>,
        prev: Option<NodePtr<'id, T>>,
        elem: T,
    }
    
    type NodePtr<'id, T> = Half<GhostCell<'id, Node<'id, T>>>;
    
    type Half<T> = StaticRc<T, 1, 2>;
    type Full<T> = StaticRc<T, 2, 2>;
    

    And I implemented a push_back and pop_back method:

        pub fn push_back(&mut self, side: usize, elem: T, token: &mut GhostToken<'id>) {
            // Create a new node and split up to two halves.
            let (left, right) = Full::split(Full::new(GhostCell::new(Node::new(elem))));
            match self.tail.take() {
                Some(tail) => {
                    // Link the left pointer. If the list is empty, link `self.head`.
                    tail.deref().borrow_mut(token).next = Some(left);
                    // Transfer the ownership of `self.tail` before insertion to the new inserted node.
                    right.deref().borrow_mut(token).prev = Some(tail);
                }
                None => self.head = Some(left),
            }
            // Link the right pointer to `self.tail`.
            self.tail = Some(right);
        }
        pub fn pop_back(&mut self, side: usize, token: &mut GhostToken<'id>) -> Option<T> {
            // Take the right pointer from `self.tail`. Or return `None` if the list is empty.
            let right = self.tail.take()?;
            let left = match right.deref().borrow_mut(token).prev.take() {
                Some(tail) => {
                    // If the previous of `self.tail` is not empty, take the left pointer from its `next`,
                    // or else take the left pointer from `self.head`.
                    let left = tail.deref().borrow_mut(token).next.take().unwrap();
                    // Relink `self.tail`.
                    self.tail = Some(tail);
                    left
                }
                None => self.head.take().unwrap(),
            };
            // Join the left and right pointers, and returns the  popped node.
            Some(Full::into_box(Full::join(left, right)).into_inner().elem)
        }
    

    Everything worked fine until I tried to implement a mutable iterator for it. First I wrote:

    pub struct IterMut<'id, 'iter, T> {
        head: Option<&'iter NodePtr<'id, T>>,
        tail: Option<&'iter NodePtr<'id, T>>,
        len: usize,
        token: &'iter mut GhostToken<'id>,
    }
    
    impl<'id, 'iter, T> Iterator for IterMut<'id, 'iter, T> {
        type Item = &'iter mut T;
    
        fn next(&mut self) -> Option<Self::Item> {
            let current = self.head?;
            self.head = current.deref().borrow(self.token).next;
            self.len -= 1;
            // Compiler error: `token` was borrowed immutable first then mutable.
            Some(&current.deref().borrow_mut(self.token).elem)
        }
    }
    

    In alternative, I tried another way to implement the mutable iterator via an embedded way (similar to Iterator::for_each).

        pub fn for_each_mut(&self, token: &mut GhostToken<'id>, mut f: impl FnMut(&mut T)) {
            let mut current = self.head.as_ref();
            while let Some(node) = current {
                let node = node.deref().borrow_mut(token);
                f(&mut node.elem);
                current = node.deref().next.as_ref();
            }
        }
    

    And the compiler complained:

    error[E0499]: cannot borrow `*token` as mutable more than once at a time
       --> src/experiments.rs:182:48
        |
    182 |             let node = node.deref().borrow_mut(token);
        |                                                ^^^^^ `*token` was mutably borrowed here in the previous iteration of the loop
    

    Actually, in this line, let node = node.deref().borrow_mut(token); I only needed to borrow the elem of the node inside the loop body. but current = node.deref().next.as_ref(); extended the lifetime of the borrowed node, so in the while loop, the token was multiply borrowed mutably, and yielded a compile error.

    To solve this problem, I have already tried to temporarily take the ownership of the next pointer by Option::take, then split it up to two type Quarter<T, 1, 4>s. It then led to another problem. Since the next of the next pointer must borrow from the splitted pointer, it cannot be joined together again in the loop body. I have to store all of the splitted pointers during the loop, and joined back them after the end of the loop. It can bring extra time and memory cost, which is definitely unacceptable.


    That's all the background where I opened this issue.

    Anyway, StaticRc is a great and intelligent invention! And hope it becomes more powerful in the future! :)

    opened by frank-king 1
  • Consider to design `StaticRc` as a linear type?

    Consider to design `StaticRc` as a linear type?

    In current implementation, a StaticRc<T, NUM, DEN> where NUM < DEN, it will do nothing on Drop. It can probably lead to memory leak if a "full" pointer is split up to two, and the two pointers are dropped silently.

    How about to design the StaticRc<T, NUM, DEN> to be a linear type (i.e., it must be use and only use once). To say an object is fully used in the static reference counting system means: it is created as a full pointer like Box, and might be split up to several sub-pointers that shares the ownity, but must finally joined into a full pointer again, then the pointer is automatically dropped.

    So the main implementation idea is: if I drop a non-full pointer, there will be a compile-time error because it is the object is not fully used.

    opened by frank-king 5
  • Add examples

    Add examples

    HI there! I figured I'd take a shot at building a doubly-linked list with static-rc, and it turned out to be pretty difficult. The main difficulty is in constructing reference cycles, as it's not possible to have more than two pointers to any list element.

    Is there an example of this, or any other similar data structure? Could that be added to the docs?

    opened by djmitche 3
Owner
null
Avoid double indirection in nested smart pointers.

Pierce Avoid double indirection in nested smart pointers. The Pierce stuct allows you to cache the deref result of doubly-nested smart pointers. Quick

null 11 Jan 12, 2022
wait-free spsc linked-list queue with individually reusable nodes

A wait-free single-producer single-consumer linked-list queue with individually reusable nodes.

glowcoil 22 Dec 26, 2022
Doubly-Linked List Implementation in Rust

Doubly-Linked List Implementation in Rust Purely for educational and recreational purposes. For real world production please use std::collections::Lin

Tsoding 9 Jul 28, 2022
Cyclic Double-Linked List

Double-Linked List This crate provides a doubly-linked list with owned nodes, implemented as a cyclic list. Usage First, add dependency in your Cargo.

Frank King 10 Nov 26, 2021
A doubly-linked list for Rust backed by generational-arena.

generational_token_list A doubly-linked list backed by generational-arena. Inspired by indexlist. Instead of returning pointers or numerical indices t

null 9 Dec 10, 2022
Display linked packages for compiled rust binaries

cargo-linked Display the packages a rust binary is linked against. As cargo subcommand! Easy said: run cargo linked to find out which packages you mus

Jojii 1 Jun 7, 2021
Rust Vec Doubly Linked List

Rust Vec Doubly Linked List Just like doubly linked list(e.g. std::LinkedList), but supports that returning a index of the vec when push. And you can

~ 2 May 1, 2022
A Rust doubly-linked intrusive list with Miri tests

Intrusive stack-allocated doubly-linked list example Quickstart cargo test Or to run the tests under Miri: rustup toolchain install nightly # if y

Cliff L. Biffle 9 Dec 4, 2022
Broot - A new way to see and navigate directory trees

Broot A better way to navigate directories Installation Instructions Get an overview of a directory, even a big one br -s Notice the unlisted? That's

Canop 8k Jan 8, 2023
Library containing various Data Structures implemented using Rust.

rust-data-structures Library containing various Data Structures implemented using Rust. Running You can test the library by running cargo test, an exa

c1m50c 1 Jan 6, 2022
All Algorithms implemented in Rust

The Algorithms - Rust All algorithms implemented in Rust (for educational purposes) These are for demonstration purposes only. RESTART BUILD Sort Algo

The Algorithms 13.1k Dec 26, 2022
Recursive & Iterative Binary Search Tree Implementations within Rust

bst-rs Recursive & Iterative Binary Search Tree Implementations within Rust Table of Contents Personal Goals About Quick Start License Contributing In

Hamothy 6 Oct 1, 2022
Additional Rust collections not found in std::collections

More collections Rust crate with additional collections not found in std::collections. Multimaps Completion Name Behaves like ?? ?? ?? ⬜️ ⬜️ HashSetMu

Rinde van Lon 4 Dec 21, 2022
Learn Rust by writing Entirely Too Many linked lists

Learn Rust by writing Entirely Too Many Linked Lists Read the pretty version at https://rust-unofficial.github.io/too-many-lists/. Building Building r

null 2.4k Jan 3, 2023
Windows Linked Lists in idiomatic Rust (LIST_ENTRY, SINGLE_LIST_ENTRY)

nt-list by Colin Finck <[email protected]> Provides compatible, type-safe, and idiomatic Rust implementations of the Windows NT Linked Lists, known as

Colin Finck 13 Feb 25, 2023
Some collections to store a fixed number of elements of a specific type.

This repo provide some useful data-structures, such as: Array, HashMap, HashSet, BTreeMap, BTreeSet, etc... That lives on the stack. It's a good choic

null 0 Nov 2, 2022
The most fundamental type for async synchronization: an intrusive linked list of futures

wait-list This crate provides WaitList, the most fundamental type for async synchronization. WaitList is implemented as an intrusive linked list of fu

Sabrina Jewson 7 Oct 26, 2022
suidsnoop is a tool based on eBPF LSM programs that logs whenever a suid binary is executed and implements custom allow/deny lists.

suidsnoop Log suid binaries and enforce per-uid suid policy. suidsnoop is a tool for logging whenever a suid binary is executed on your system and opt

William Findlay 11 Dec 22, 2022
Python module implemented in Rust for counting the number of one bits in a numpy array.

bit-counter Package for counting the number of one bits in a numpy array of uint8 values. Implemented as a Python module using Rust, providing high pe

Andrew MacDonald 3 Jul 9, 2022
Rust+Cargo lightweight hello world with the most minimum binary size possible.

Lightweight Cargo Hello World Rust+Cargo lightweight hello world with the most minimum binary size possible. requirements 1: Rustup (Rustc, Cargo) Ins

Raymond 1 Dec 13, 2021