Most of the design work and discussion on pin projection has focused on implementing the feature with minimal specialized language support, using macros or non-specialized language features to accomplish as much as possible. What if instead, Rust went all in on making Pin
a core language type? I explore such a design below.
Places today
Before discussing how to extend Rust's places to better support Pin
, we need to review how they work today.
Properties of places
In today's surface Rust, a place is defined by the following attributes:
- The type of its pointee
- Its memory address
- Its lifetime
- The capabilities it possesses described below. We will call the possible capabilities mutable, movable, and unpinned; the next section describes each.
Operations on places
Different operations on places require different combinations of the aforementioned capabilities:
addr_of!(place)
and field projection (viaplace.field
or match destructuring): no capability requirements, all places can do it.addr_of_mut!(place)
: requires mutable.- place-to-value coercion: requires movable.
&place
(orref place
in a match): requires unpinned, and the lifetime of the resulting reference cannot exceed that of the place itself.&mut place
(orref mut place
in a match): requires unpinned AND mutable, and the lifetime of the resulting reference cannot exceed that of the place itself.
Field projection (via place.field
or match destructuring) produces a new place with different capabilities, based on the following rules:
- A projected place is mutable iff its parent place was mutable.
- A projected place is movable iff (its parent place was movable AND no implementation of
Drop
exists for the type of the parent place) OR (the type of the projected place implementsCopy
). - A projected place is unpinned iff its parent was unpinned AND it is not a potentially-unaligned member of a
#[repr(packed)]
ADT.
Examples of places
Here are some examples of places with different combinations of capabilities:
use core::ptr::{addr_of, addr_of_mut};
struct NotCopy(u16);
struct Wrapper(u16, NotCopy);
#[repr(packed)]
struct PackedWrapper(u16, NotCopy);
fn foo(w: &Wrapper, mw: &mut Wrapper, pw: &PackedWrapper, mpw: &mut PackedWrapper) {
// `w.0` is by-value and unpinned
addr_of!(w.0);
w.0;
&w.0;
// `w.1` is unpinned
addr_of!(w.1);
&w.1;
// `mw.0` is by-value, unpinned, and mutable
addr_of!(mw.0);
addr_of_mut!(mw.0);
mw.0;
&mw.0;
&mut mw.0;
// `mw.1` is unpinned and mutable
addr_of!(mw.1);
addr_of_mut!(mw.1);
&mw.1;
&mut mw.1;
// `pw.0` is by-value
addr_of!(pw.0);
pw.0;
// `pw.1` has no capabilities
addr_of!(pw.1);
// `mpw.0` is by-value and mutable
addr_of!(mpw.0);
addr_of_mut!(mpw.0);
mpw.0;
// `mpw.1` is mutable
addr_of!(mpw.1);
addr_of_mut!(mpw.1);
}
New additions for pin projection
To support pin projections, a new place capability, pinned, is added. (This capability is not mutually exclusive with unpinned).
Producing a pinned place
A pinned place can be produced in the following ways:
- Any unpinned place whose type implements
Unpin
is also pinned (and any pinned place whose type implementsUnpin
is also unpinned). - Any unsafe place (result of a raw pointer dereference, or
static mut
reference) is pinned (as well as unpinned). - Dereferencing a
Pin<P>
whereP: Deref<Target = T>
produces a pinned place of typeT
. (IfP: DerefMut
, the place is also mutable. IfT: Unpin
, the place is also unpinned.) - Projection from a pinned place via a
#[pin]
field (explained later) produces a pinned place.
New operations on pinned places
&place
: requires unpinned OR pinned, and the lifetime of the resulting reference cannot exceed that of the place itself.&pin place
(orref pin place
in a match): this new operator produces aPin<&TypeOfPlace>
. It requires pinned—unless the resulting reference is to have a'static
lifetime, in which case unpinned is also acceptable. The lifetime of the resulting reference cannot exceed that of the place itself.&pin mut place
(orref pin mut place
in a match): this new operator produces aPin<&mut TypeOfPlace>
. It requires pinned AND mutable—unless the resulting reference is to have a'static
lifetime, in which case unpinned AND mutable is also acceptable. The lifetime of the resulting reference cannot exceed that of the place itself.
Projecting from a pinned place
Any field of an ADT can be annotated with at most one of the attributes #[pin]
or #[unpin]
, unless it is a potentially unaligned member of a #[repr(packed)]
ADT.
The projection rules for pinned and unpinned are as follows:
- A projected place is unpinned iff (its parent was unpinned OR (its parent was pinned AND (the field is annotated with
#[unpin]
OR the field's type implementsUnpin
))) AND the field is not a potentially-unaligned member of a#[repr(packed)]
ADT. - A projected place is pinned iff ((its parent was unpinned AND the field's type implements
Unpin
) OR (its parent was pinned AND (the field is annotated with#[pin]
OR the field's type implementsUnpin
))) AND the field is not a potentially-unaligned member of a#[repr(packed)]
ADT.
Reborrows
Automatic reborrows are extended to support pinned pointers, wherever the reborrow could be written out explicitly with &pin
and the new projections.
Drop
The following changes are made to the Drop
trait:
- Implementations of
Drop::drop()
can choose to use a signature of eitherfn drop(&mut self)
ORfn drop(self: Pin<&mut Self>)
. - However, if the implementing type is an ADT with at least one field annotated with
#[pin]
, only thefn drop(self: Pin<&mut Self>)
signature is permitted.
What it looks like
Here are some examples:
use core::{future::Future, marker::PhantomPinned, pin::Pin, ptr::addr_of_mut, task::Context};
use futures::future;
fn foo(ctx: &mut Context<'_>) {
let mut r = future::ready(42_u8);
// Automatic `&pin mut` borrow:
// `u8: Unpin`, so place `r` is pinned
r.poll(ctx);
}
fn poll_twice(f: Pin<&mut impl Future>, ctx: &mut Context<'_>) {
// f is reborrowed, not moved
f.poll(ctx);
f.poll(ctx);
}
struct Projections {
#[pin]
pinned: PhantomPinned,
#[unpin]
unpinned: PhantomPinned,
both: u8,
neither: PhantomPinned,
}
impl Projections {
// or perhaps `&pin mut self`?
fn bar(self: Pin<&mut Self>) {
let _: Pin<&mut PhantomPinned> = &pin mut self.pinned;
let _: &mut PhantomPinned = &mut self.unpinned;
let _: &mut u8 = &mut self.both;
// No need fot `&pin mut`, implicit reborrow as `Pin`
let _: Pin<&mut u8> = &mut self.both;
let _: *mut PhantomPinned = addr_of_mut!(self.neither);
}
}
impl Drop for Projections {
fn drop(self: Pin<&mut Self>) {
/* ... */
}
}