#stack #pinned #data #point #type #declaration #unpin

bin+lib stackpin

Crate for data that should be pinned to the stack at the point of declaration

2 releases

0.0.2 Oct 12, 2019
0.0.1 Oct 12, 2019

#915 in Rust patterns

35 downloads per month
Used in 2 crates

MIT/Apache

34KB
450 lines

stackpin

This crate exposes a StackPinned type that allows to represent !Unpin data that should be pinned to the stack at the point of declaration.

To do so, this crate provides a FromUnpinned trait and a stack_let! macro that enable safe construction of Pin<StackPinned> objects (aliased to PinStack for short).

Getting instances pinned at the point of declaration is as easy as:

stack_let!(unmovable = Unmovable::new_unpinned("Intel the Beagle")); // this creates the unmovable instance on the stack and binds `unmovable` with a `PinStack<Unmovable>`

For Unmovable a struct implementing the FromUnpinned<String> trait.

See the crate documentation for details, or look directly at the examples.


lib.rs:

The stackpin crate exposes a StackPinned type that allows to represent !Unpin data that should be pinned to the stack at the point of declaration. The crate exposes a trait, FromUnpinned, as well as a stack_let macro that makes safely creating StackPinned instances easier. The crate also exposes the PinStack type alias for Pin<StackPinned<T>>.

This crate was inspired from the pin-utils crate, the main differences being:

  • pin-utils provides a macro to return a Pin<&mut T> instance, with a "mutable reference" semantics that includes reborrow. The stackpin crate promotes a "root handle" semantics that guarantees that a function consuming a PinStack<T> consumes the only handle to T, and not a reborrowed reference.
  • The syntax for the stack_let!(mut id : ty = expr) macro attempts to mimic a regular let mut id : ty = expr statement.
  • The provided FromUnpinned trait and Unpinned struct aim at separating unmovable types from the data that can be used to construct them. stackpin aims at promoting a model where all unmovable types are only accessible once pinned.
  • The StackPinned<T> type expresses strong guarantee about the fact that the destructor for T will be run.
  • The stackpin crate solely focuses on stack pinning. The pin-utils crate also provides other utilities such as pin projection.

Stack pinnable types

A type T that wants to benefit from the guarantees provided by StackPinned should be !Unpin. This is necessary to enforce the "drop will be run" guarantee.

Additionally, the stackpin crate promotes an idiom where "unmovable" types are strictly separated from movable types, and are preferably only accessible through PinStack.

For example, let's consider the following Unmovable struct (from the documentation for the pin module):

use std::marker::PhantomPinned;
use std::ptr::NonNull;
struct Unmovable {
    // Owned data
    s: String,
    // Self referential pointer meant to point to `s`
    slice: NonNull<String>,
    // Obligatory marker that makes this struct `!Unpin`.
    // Without this, implementing `FromUnpinned` for `Unmovable` would not be safe.
    _pinned: PhantomPinned,
}

It is important to note that this struct is not unmovable by itself, as there are no such types in Rust. Instead, we are going to enforce this through privacy: since the fields of the struct are private, no instance can be created from outside the module. Similarly, no public "constructor" function pub fn new() -> Unmovable should be provided.

So, how will clients consume Unmovable instances?

The recommended solution using stackpin is to implement FromUnpinned<Data> for Unmovable, where Data is the type that would normally serve as parameters in a "constructor" function.

# use std::marker::PhantomPinned;
# use std::ptr::NonNull;
# struct Unmovable {
#     s: String,
#     slice: NonNull<String>,
#     _pinned: PhantomPinned,
# }
use stackpin::FromUnpinned;
// An `Unmovable` can be created from a `String`
unsafe impl FromUnpinned<String> for Unmovable {
    // This associated type can be used to retain information between the creation of the instance and its pinning.
    // This allows for some sort of "two-steps initialization" without having to store the initialization part in the
    // type itself.
    // Here, we don't need it, so we just set it to `()`.
    type PinData = ();

    // Simply builds the Unmovable from the String.
    // The implementation of this function is not allowed to consider that the type won't ever move **yet**.
    // (in particular, the `Self` instance is returned by this function)
    // Note, however, that safe users of FromUnpinned will:
    // * Not do anything to with the returned `Self` instance between the call to
    //   `from_unpinned` and the call to `on_pin`.
    // * Not panic between calling the two functions
    // * Always call the second function if the first has been called.
    unsafe fn from_unpinned(s: String) -> (Self, ()) {
        (
            Self {
                s,
                // We will "fix" this dangling pointer once the data will be pinned
                // and guaranteed not to move anymore.
                slice: NonNull::dangling(),
                _pinned: PhantomPinned,
            },
            (),
        )
    }

    // Performs a second initialization step on an instance that is already guaranteed to never move again.
    // This allows to e.g. set self borrow with the guarantee that they will remain valid.
    unsafe fn on_pin(&mut self, _data: ()) {
        // Data will never move again, set the pointer to our own internal String whose address
        // will never change anymore
        self.slice = NonNull::from(&self.s);
    }
}

With FromUnpinned<Data> implemented for T, one can now add a "constructor method" that would return an Unpinned<Data, T>. The Unpinned<U, T> struct is a simple helper struct around U that maintains the destination type T. This is used by the stack_let macro to infer the type of T that the user may want to produce.

# use std::marker::PhantomPinned;
# use std::ptr::NonNull;
# struct Unmovable {
#     s: String,
#     slice: NonNull<String>,
#     _pinned: PhantomPinned,
# }
# use stackpin::Unpinned;
# use stackpin::FromUnpinned;
# unsafe impl FromUnpinned<String> for Unmovable {
#     type PinData = ();
#     unsafe fn from_unpinned(s: String) -> (Self, ()) {
#         (
#             Self {
#                 s,
#                 slice: NonNull::dangling(),
#                 _pinned: PhantomPinned,
#             },
#             (),
#         )
#     }
#     unsafe fn on_pin(&mut self, _data: ()) {
#         self.slice = NonNull::from(&self.s);
#     }
# }
impl Unmovable {
    fn new_unpinned<T: Into<String>>(s: T) -> Unpinned<String, Unmovable> {
       Unpinned::new(s.into())
    }
}

Then, a user of the Unmovable struct can simply build an instance by using the stack_let macro:

# use std::marker::PhantomPinned;
# use std::ptr::NonNull;
# struct Unmovable {
#     s: String,
#     slice: NonNull<String>,
#     _pinned: PhantomPinned,
# }
# use stackpin::Unpinned;
# use stackpin::FromUnpinned;
# unsafe impl FromUnpinned<String> for Unmovable {
#     type PinData = ();
#     unsafe fn from_unpinned(s: String) -> (Self, ()) {
#         (
#             Self {
#                 s,
#                 slice: NonNull::dangling(),
#                 _pinned: PhantomPinned,
#             },
#             (),
#         )
#     }
#     unsafe fn on_pin(&mut self, _data: ()) {
#         self.slice = NonNull::from(&self.s);
#     }
# }
# impl Unmovable {
#     fn new_unpinned<T: Into<String>>(s: T) -> Unpinned<String, Unmovable> {
#        Unpinned::new(s.into())
#     }
# }
use stackpin::stack_let;
// ...
stack_let!(unmovable = Unmovable::new_unpinned("Intel the Beagle")); // this creates the unmovable instance on the stack and binds `unmovable` with a `PinStack<Unmovable>`
// ...

No runtime deps