pin_project
allows struct designers to get pinned references to struct fields out of a pinned reference to the whole struct. It further allows the designer to specify which fields need pinned references and which can simply get a regular reference:
#[pin_project]
struct Demo<A, B> {
#[pin]
field_a: A,
field_b: B,
}
impl<A, B> Demo {
fn show_references(self: Pin<&mut Self>) {
let this = self.project();
let field_a: Pin<&mut A> = this.field_a;
let field_b: &mut B = this.field_b;
}
}
My question is how can the field_b
part be sound without B: Unpin
? The creation of a pinned reference "pins" the struct, mandating that the referenced struct must not move out of its location in memory. Wouldn't this property apply recursively, in the same way that mutability does? If Demo
is pinned, doesn't that mean that field_a
and field_b
are implicitly pinned?
As described in the documentation for pin projection, the guarantee that Pin
makes is not necessarily recursive.
The primary guarantee here is that "Pin<P>
prevents certain values (pointed to by pointers wrapped in Pin<P>
) from being moved." By design, this applies specifically to those values as a whole, with Pin
itself making no claims about the fields of those values.
Pin
could have been designed differently, but this particular approach is quite useful, because it lets each particular use case decide for itself whether it needs "structural" pinning (where a field must be pinned on its own) and "non-structural" pinning (where you can safely move or swap a field).
As an example, imagine a value of type PinMe
that must be pinned, and put this value into a struct Wrapper
. Pointers to such Wrapper
values must be Pin
pointers, to prevent moving the inner PinMe
:
#[pin_project]
struct Wrapper {
#[pin]
pinned: PinMe,
}
fn f(w: Pin<&mut Wrapper>) {
// We cannot move the `Wrapper` that `w` points to, as that would move `w.pinned`.
// All we can do is grab a pinned pointer to the `PinMe`:
let inner: Pin<&mut PinMe> = w.project().pinned;
}
But if Wrapper
has another field, completely unrelated to PinMe
, there is no reason not to move or swap that field:
#[pin_project]
struct Wrapper {
#[pin]
pinned: PinMe,
counter: i32,
}
fn f(w: Pin<&mut Wrapper>) {
let w = w.project();
let inner: Pin<&mut PinMe> = w.pinned;
let counter: &mut i32 = w.counter;
// These sorts of operations do not affect the `PinMe`:
*counter += 3;
mem::replace(counter, 5);
}
The choice of structural vs non-structural pinning depends entirely on the invariants you need to uphold. If field_b
needs to remain right next to field_a
, then add #[pin]
to it. But if that isn't something your Demo
type needs, you can leave it out, which provides fewer guarantees but is still safe.
Edit: Further, this applies even if that extra field does not implement Unpin
, so long as nothing has constructed a Pin
pointing directly to it. For example:
#[pin_project]
struct Wrapper<T> {
#[pin]
pinned: PinMe,
// We know nothing about this type except that it is `Sized`.
// Thus, we cannot assume it implements `Unpin`.
counter: T,
}
fn f<T>(w: Pin<&mut Wrapper<T>>, other: T) {
let w = w.project();
let inner: Pin<&mut PinMe> = w.pinned;
let counter: &mut T = w.counter;
// This is still okay:
mem::replace(counter, other);
}