Another look at the pinning API

A few months ago we introduced the concept of “pinned” references - pointers which “pin” the data they refer to in a particular memory location, guaranteeing that it will never move again. These are an important building block for certain patterns that had previously been hard for Rust to handle, like self-referential structs and intrusive lists, and we’ve in the process of considering stabilizing the API.

One thing has always nagged about the API we have right now though: the proliferation of different reference types that it implies. Today, the pin feature adds the PinMut and PinBox types, but in theory there ought to be a “pinned” version of every pointer in the standard library: PinRc and PinArc and so on. This is a very unfortunate consequence, but so far we have not found a good way to make pinning work compositionally - to have a single adapter that could be combined with any pointer.

Last night, a bit of inspiration struck me, and I realized that it is possible to make a compositional Pin type. This isn’t a fundamental change to the pinning model, just an API refactoring, but I’ve put a blocking concern on the proposal to stabilize Pin so that we can consider this possibility.

The Pin wrapper

In this new API, PinBox and PinMut would both disappear, and be replaced by a single type: Pin<P>. I’ve named the parameter to Pin P instead of T to help readers remember that Pin is generic over a pointer type, not any type at all. So PinBox<T> is replaced by Pin<Box<T>>, and PinMut<'a, T> by Pin<&'a mut T>. Similarly, this can be extended to Pin<&'a T>, Pin<Rc<T>> and Pin<Arc<T>>.

Here’s a bit of code:

pub struct Pin<P> {
    pointer: P,
}

impl<P, T> Deref for Pin<P> where
    P: Deref<Target = T>,
{
    type Target = T;
    fn deref(&self) -> &T {
        &*self.pointer
    }
}

impl<P, T> DerefMut for Pin<P> where
    P: DerefMut<Target = T>,
    T: Unpin,
{
    fn deref_mut(&mut self) -> &mut T {
        &mut *self.pointer
    }
}

Like the previous PinBox and PinMut types, Pin implements Deref and DerefMut. It only implements DerefMut if the target type implements Unpin. This is what makes the whole pinning system safe at all.

impl<P, T> Pin<P> where
    P: Deref<Target = T>,
{
    pub fn as_ref(&self) -> Pin<&T> {
        Pin { pointer: &*self.pointer }
    }
}

impl<P, T> Pin<P> where
    P: DerefMut<Target = T>,
{
    pub fn as_mut(&mut self) -> Pin<&mut T> {
        Pin { pointer: &mut *self.pointer }
    }

    pub unsafe fn get_mut_unchecked(&mut self) -> &mut T {
        &mut *self.pointer
    }
}

It’s also possible to convert a Pin<P> to a Pin<&T> and Pin<&mut T>, regardless of whether or not the target is Unpin. This subsumes both PinBox::as_pin_mut and PinMut::reborrow: they are both the same underlying operation on the Pin type, allowing you to borrow access to the pinned data (mutably and immutably) without taking it out of the Pin.

All of the API above is essentially the same as the API of PinBox and PinMut: nothing new has been added here. The real trick to make this work is in the constructors for Pin.

Constructing a Pin

The big problem with the idea of compositional Pin before was that different pointers are safe to pin in different ways. For example, this implementation is perfectly safe:

impl<T> From<Box<T>> for Pin<Box<T>> {
    fn from(b: Box<T>) -> Pin<Box<T>> {
        Pin { pointer: b }
    }
}

But the same impl with Rc replacing Box would not be - a user could clone two aliased Rc’s, pin one, drop it, and then move the value again out of the other Rc they’ve been holding onto.

We can make this work by implementing a single core unsafe constructor, and for each pointer type we implement an appropriate constructor on top of it:

impl<P> Pin<P> {
    unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer: pointer }
    }
}

impl<T> Pin<Box<T>> {
    pub fn new_box(data: T) -> Pin<Box<T>> {
        unsafe { Pin::new_unchecked(Box::new(data)) }
    }
}

impl<T> Pin<Rc<T>> {
    pub fn new_rc(data: T) -> Pin<Rc<T>> {
        unsafe { Pin::new_unchecked(Rc::new(data)) }
    }
}

// etc

If end users want to be able to pin their own pointer types, they can create a new constructor that returns a Pin<MyPointer<T>>, reusing all of the rest of the pin API with their type. And if you want to abstract over multiple kinds of pinned pointers, its possible with something like Pin<P> where P: DerefMut.

An unsafe Own trait

We can even go a step further: while some constructors (like Box<T> -> Pin<Box<T>>) are not generally safe for different pointer types, there are some that we could reasonably abstract. T -> Pin<Box<T>> and T -> Pin<Rc<T>> have the same shape after all.

We can do this with an unsafe trait, which I’ll call Own. A pointer implements Own if it takes ownership of the data and never moves it from that address again until it is destroyed, even if the pointer type is moved. All of Box, Rc and Arc meet this requirement.

unsafe trait Own: Deref {
    fn own(data: Self::Target) -> Self;

    fn pinned(data: Self::Target) -> Pin<Self> {
        unsafe { Pin::new_unchecked(Self::own(data)) }
    }
}

With this trait, there’s a uniform interface for creating pinned, owned pointers. PinBox::new becomes Box::pinned, and you can also write Rc::pinned and Arc::pinned. If you define a pointer that meets these requirements, you can unsafe impl Own for your own type as well.

Conclusion

I’ve written up a gist as well containing the full API (less various helpers and impl like Debug and map_unchecked) that I’d propose to replace PinBox and PinMut. I’m interested in hearing peoples' thoughts!