2 releases
0.0.2 | Oct 12, 2019 |
---|---|
0.0.1 | Oct 12, 2019 |
#2153 in Rust patterns
Used in 2 crates
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. Thestackpin
crate promotes a "root handle" semantics that guarantees that a function consuming aPinStack<T>
consumes the only handle toT
, and not a reborrowed reference. - The syntax for the
stack_let!(mut id : ty = expr)
macro attempts to mimic a regularlet mut id : ty = expr
statement. - The provided
FromUnpinned
trait andUnpinned
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 forT
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 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.
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 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>`
// ...