Description
@rfcbot fcp merge
Feature name: pin
Stabilization target: 1.32.0
Tracking issue: #49150
Related RFCs: rust-lang/rfcs#2349
This is a proposal to stabilize the pin
library feature, making the "pinning"
APIs for manipulating pinned memory usable on stable.
(I've tried to write this proposal as a comprehensive "stabilization report.")
Stabilized feature or APIs
[std|core]::pin::Pin
This stabilizes the Pin
type in the pin
submodule of std
/core
. Pin
is
a fundamental, transparent wrapper around a generic type P
, which is intended
to be a pointer type (for example, Pin<&mut T>
and Pin<Box<T>>
are both
valid, intended constructs). The Pin
wrapper modifies the pointer to "pin"
the memory it refers to in place, preventing the user from moving objects out
of that memory.
The usual way to use the Pin
type is to construct a pinned variant of some
kind of owning pointer (Box
, Rc
, etc). The std library owning pointers all
provide a pinned
constructor which returns this. Then, to manipulate the
value inside, all of these pointers provide a way to degrade toward Pin<&T>
and Pin<&mut T>
. Pinned pointers can deref, giving you back &T
, but cannot
safely mutably deref: this is only possible using the unsafe get_mut
function.
As a result, anyone mutating data through a pin will be required to uphold the
invariant that they never move out of that data. This allows other code to
safely assume that the data is never moved, allowing it to contain (for
example) self references.
The Pin
type will have these stabilized APIs:
impl<P> Pin<P> where P: Deref, P::Target: Unpin
fn new(pointer: P) -> Pin<P>
impl<P> Pin<P> where P: Deref
unsafe fn new_unchecked(pointer: P) -> Pin<P>
fn as_ref(&self) -> Pin<&P::Target>
impl<P> Pin<P> where P: DerefMut
fn as_mut(&mut self) -> Pin<&mut P::Target>
fn set(&mut self, P::Target);
impl<'a, T: ?Sized> Pin<&'a T>
unsafe fn map_unchecked<U, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
fn get_ref(self) -> &'a T
impl<'a, T: ?Sized> Pin<&'a mut T>
fn into_ref(self) -> Pin<&'a T>
unsafe fn get_unchecked_mut(self) -> &'a mut T
unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>
impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin
fn get_mut(self) -> &'a mut T
Trait impls
Most of the trait impls on Pin
are fairly rote, these two are important to
its operation:
impl<P: Deref> Deref for Pin<P> { type Target = P::Target }
impl<P: DerefMut> DerefMut for Pin<P> where P::Target: Unpin { }
std::marker::Unpin
Unpin is a safe auto trait which opts out of the guarantees of pinning. If the
target of a pinned pointer implements Unpin
, it is safe to mutably
dereference to it. Unpin
types do not have any guarantees that they will not
be moved out of a Pin
.
This makes it as ergonomic to deal with a pinned reference to something that
does not contain self-references as it would be to deal with a non-pinned
reference. The guarantees of Pin
only matter for special case types like
self-referential structures: those types do not implement Unpin
, so they have
the restrictions of the Pin
type.
Notable implementations of Unpin
in std:
impl<'a, T: ?Sized> Unpin for &'a T
impl<'a, T: ?Sized> Unpin for &'a mut T
impl<T: ?Sized> Unpin for Box<T>
impl<T: ?Sized> Unpin for Rc<T>
impl<T: ?Sized> Unpin for Arc<T>
These codify the notion that pinnedness is not transitive across pointers. That
is, a Pin<&T>
only pins the actual memory block represented by T
in a
place. Users have occassionally been confused by this and expected that a type
like Pin<&mut Box<T>>
pins the data of T
in place, but it only pins the
memory the pinned reference actually refers to: in this case, the Box
's
representation, which a pointer into the heap.
std::marker::Pinned
The Pinned
type is a ZST which does not implement Unpin
; it allows you to
supress the auto-implementation of Unpin
on stable, where !Unpin
impls
would not be stable yet.
Smart pointer constructors
Constructors are added to the std smart pointers to create pinned references:
Box::pinned(data: T) -> Pin<Box<T>>
Rc::pinned(data: T) -> Pin<Rc<T>>
Arc::pinned(data: T) -> Pin<Arc<T>>
Notes on pinning & safety
Over the last 9 months the pinning APIs have gone through several iterations as
we have investigated their expressive power and also the soundness of their
guarantees. I would now say confidently that the pinning APIs stabilized here
are sound and close enough to the local maxima in ergonomics and
expressiveness; that is, ready for stabilization.
One of the trickier issues of pinning is determining when it is safe to perform
a pin projection: that is, to go from a Pin<P<Target = Foo>>
to a
Pin<P<Target = Bar>>
, where Bar
is a field of Foo
. Fortunately, we have
been able to codify a set of rules which can help users determine if such a
projection is safe:
- It is only safe to pin project if
(Foo: Unpin) implies (Bar: Unpin)
: that
is, if it is never the case thatFoo
(the containing type) isUnpin
while
Bar
(the projected type) is notUnpin
. - It is only safe if
Bar
is never moved during the destruction ofFoo
,
meaning that eitherFoo
has no destructor, or the destructor is carefully
checked to make sure that it never moves out of the field being projected to. - It is only safe if
Foo
(the containing type) is notrepr(packed)
,
because this causes fields to be moved around to realign them.
Additionally, the std APIs provide no safe way to pin objects to the stack.
This is because there is no way to implement that safely using a function API.
However, users can unsafely pin things to the stack by guaranteeing that they
never move the object again after creating the pinned reference.
The pin-utils
crate on crates.io contains macros to assist with both stack
pinning and pin projection. The stack pinning macro safely pins objects to the
stack using a trick involving shadowing, whereas a macro for projection exists
which is unsafe, but avoids you having to write the projection boilerplate in
which you could possibly introduce other incorrect unsafe code.
Implementation changes prior to stabilization
- Export
Unpin
from the prelude, removepin::Unpin
re-export
As a general rule, we don't re-export things from multiple places in std unless
one is a supermodule of the real definition (e.g. shortening
std::collections::hash_map::HashMap
to std::collections::HashMap
). For this
reason, the re-export of std::marker::Unpin
from std::pin::Unpin
is out of
place.
At the same time, other important marker traits like Send and Sync are included
in the prelude. So instead of re-exporting Unpin
from the pin
module, by
putting in the prelude we make it unnecessary to import std::marker::Unpin
,
the same reason it was put into pin
.
- Change associated functions to methods
Currently, a lot of the associated function of Pin
do not use method syntax.
In theory, this is to avoid conflicting with derefable inner methods. However,
this rule has not been applied consistently, and in our experience has mostly
just made things more inconvenient. Pinned pointers only implement immutable
deref, not mutable deref or deref by value, limiting the ability to deref
anyway. Moreover, many of these names are fairly unique (e.g. map_unchecked
)
and unlikely to conflict.
Instead, we prefer to give the Pin
methods their due precedence; users who
need to access an interior method always can using UFCS, just as they would be
required to to access the Pin methods if we did not use method syntax.
- Rename
get_mut_unchecked
toget_unchecked_mut
The current ordering is inconsistent with other uses in the standard library.
- Remove
impl<P> Unpin for Pin<P>
This impl is not justified by our standard justification for unpin impls: there is no pointer direction between Pin<P>
and P
. Its usefulness is covered by the impls for pointers themselves.
This futures impl will need to change to add a P: Unpin
bound.
- Mark
Pin
asrepr(transparent)
Pin should be a transparent wrapper around the pointer inside of it, with the same representation.
Connected features and larger milestones
The pin APIs are important to safely manipulating sections of memory which can
be guaranteed not to be moved out. If the objects in that memory do not
implement Unpin
, their address will never change. This is necessary for
creating self-referential generators and asynchronous functions. As a result,
the Pin
type appears in the standard library future
APIs and will soon
appear in the APIs for generators as well (#55704).
Stabilizing the Pin
type and its APIs is a necessary precursor to stabilizing
the Future
APIs, which is itself a necessary precursor to stabilizing the
async/await
syntax and moving the entire futures 0.3
async IO ecosystem
onto stable Rust.
Activity