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!