Rust std study series: Pin

Rust
Author

Ehsan M. Kermani

Published

August 16, 2019

This time we dive into std::pin which has a dense documentation.

Types thatpin datato its location inmemory.

It is sometimes useful to have objects that areguaranteed to not move, in the sense that theirplacement in memory does not change, and can thus be relied upon. A prime example of such a scenario would bebuilding self-referential structs, since moving an object with pointers to itself will invalidate them, which could cause undefined behavior.

Rust std doc

Pin and Unpin

#[fundamental] // compiler thing
#[repr(transparent)] // --> ensures same memory repr as P
pub struct Pin<P> {
    pointer: P,
}
pub auto trait Unpin { }  // marker trait

To construct a Pin safely via Pin::new(pointer: P), we need to make sure that P: Deref (pointer-like) and P::Target: Unpin.

Note that Unpin ensures

Types which can besafely moved after being pinned.

Since Rust itself has no notion of immovable types, and considers moves (e.g. through assignment or mem::replace) to always be safe, this trait cannot prevent types from moving by itself.
Instead it is used toprevent moves through the type system, by controlling the behavior of pointers P wrapped in the Pin<P> wrapper, which “pin” the type in place by not allowing it to be moved out of them.

Rust std doc

Basically, the vast majority of types are “movable” i.e. Unpin, so for most cases,Pin<Pointer<T>>is exactly likePointer<T>whenT: Unpin. However, if the referent (referenced type T) is “immovable” i.e. T: !Unpin, then the pinned wrapper won’t let the underlying value to move.*Intuitively it’s like having**&mut* but with & access safety and immovability invariant. It is in fact, Unpin that controls how Pin to work.

To iterate; Pin<P> ensures that theunderlying data behind a pointer typePhas a stable location in memoryandits memory cannot be deallocated until it is dropped. So Pin pins the pointee (the data behind the pointer)not the pointer itself. Moreover,Pin<P>does not let clients actually obtain aBox<T>or&mut Tto the underlying pinned data, which implies that youcannotuse operations such as mem::swap.

Furthermore,

For correctness, Pin<P> relies on the Deref and DerefMut implementations tonot move out of theirselfparameter, and to only ever return a pointer to pinned data when they are called on a pinned pointer.

Rust std doc

To implement !Unpin for a type directly, (up to this write up) we need nightly with ![feature(optin_builtin_traits)]. The stable way is through the marker type PhantomPinned which already has impl !Unpin for PhantomPinned {}

Closer look

We cannot create Pin for a Pointer<T> where T: !Unpin safely

pub struct Pin<P> {
    pointer: P,
}
impl<P: Deref> Pin<P> 
where P::Target: Unpin // --> underlying data is movable
{
    pub fn new(pointer: P) -> Pin<P> {
        // Safety: the value pointed to is `Unpin`, and so has no requirements
        // around pinning.
        unsafe { Pin::new_unchecked(pointer) }
    }
}
impl<P: Deref> Pin<P> {
   pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

So how to pin a T: !Unpin safely?

We can use Box::pin (similarity with Rc::pin or Arc::pin) where

impl<T> Box<T> {
    #[inline(always)]
    pub fn pin(x: T) -> Pin<Box<T>> {
        (box x).into()
    }
}
impl<T: ?Sized> From<Box<T>> for Pin<Box<T>> {
    /// This conversion does not allocate on the heap and happens in place.
    fn from(boxed: Box<T>) -> Self {
        Box::into_pin(boxed)
    }
}
impl<T: ?Sized> Box<T> {
    pub fn into_pin(boxed: Box<T>) -> Pin<Box<T>> {
        // It's not possible to move or replace the insides of a `Pin<Box<T>>`
        // when `T: !Unpin`,  so it's safe to pin it directly without any
        // additional requirements.
        unsafe { Pin::new_unchecked(boxed) }
    }
}

Some notable methods of Pin areas_ref: &Pin<Pointer<T>> ---> Pin<&T>as_mut: &mut Pin<Pointer<T>> ---> Pin<&mut T>get_ref: Pin<Pointer<T>> ---> &Tget_mut: Pin<Pointer<T>> ---> &mut T (given T: Unpin)

There is also a set method which overwrites the pinned data and it might seem is invalidating the pinned construct but the subtle point is that before assigning the new value, the destructor of the old data is run so it’s ok.

impl<P: DerefMut> Pin<P> {
    pub fn set(self: &mut Pin<P>, value: P::Target)
    where
        P::Target: Sized,
    {
        *(self.pointer) = value; // --> before assignment the pointee *self.pointer is dropped
    }
}

So the whole point is, we can change the pointee Pointer::Target of a mutable pinned pointer but cannot get a &mut T out of a Pin<Pointer<T>> (Pointer::Target = T in here) when T: !Unpin.

Pin has been introduced to resolve the issues around Future and async/.await (and more general, Generator). To get a better idea, I recommend looking into async book pin section to see, for example, how an async block is turned into structs (like closures) with potentiallyself-referential mutable fieldsand how Pin helps there.

Hope we have entangled some of the complications for understanding what Pin is and what it does. For more details, checkout the complete documentation as well as more discussion on structural pinning and the safety issues around it.