rust and pin

2 minute read

Pin is one of the toughest concepts in Rust, in part due to its unfamiliarity. There was one piece of it that always had me confused. And I’ll be the first admit, after it clicking, I’m rather doubtful of my own intelligence. It’s to do with stack pinning. For a full treatment on Pin, which I happily omit, see any of these wonderful articles.

My foolishness with Pin starts here

 1use std::pin::Pin;
 2use std::marker::PhantomPinned;
 3
 4struct P {
 5    value: u32,
 6    _pin: PhantomPinned,
 7}
 8
 9fn main() {
10    let mut p = P { value: 2, _pin: PhantomPinned };
11    let pinned = unsafe { Pin::new_unchecked(&mut p) };
12
13    let y = pinned; // why is this assignment allowed?
14}

The thinking, albeit flawed, goes like this: I have an object p which I’ve dutifully pinned to the stack, called pinned. If I move pinned, I’m violating the contract. Reassigning may lead to a move in actual memory. How then, am I allowed to reassign pinned?

Well, the answer is obvious. pinned does not own p, it only holds a reference i.e the address to the value P. So if I move pinned, I don’t move the underlying value. I just keep moving around my pointer to P, no issues.

Armed with this understanding, I have further clarity on the ways in which you can violate the pinning contract. Take this example:

 1use std::pin::Pin;
 2use std::marker::PhantomPinned;
 3
 4struct P {
 5    value: u32,
 6    _pin: PhantomPinned,
 7}
 8
 9fn main() {
10    let mut p = P { value: 2, _pin: PhantomPinned };
11    let pinned = unsafe { Pin::new_unchecked(&mut p) };
12
13    let y = p;
14}

This is wrong. I have an object p and pinned it, therefore guaranteeing I won’t move it ever again. I then proceeded to reassign p, which may lead to a mechanical move in memory. Bad news. Here’s another:

 1use std::pin::Pin;
 2use std::marker::PhantomPinned;
 3
 4struct P {
 5    value: u32,
 6    _pin: PhantomPinned,
 7}
 8
 9fn main() {
10    let mut p1 = P { value: 2, _pin: PhantomPinned };
11    let mut p2 = P { value: 4, _pin: PhantomPinned };
12    let pinned = unsafe { Pin::new_unchecked(&mut p1) };
13
14    std::mem::swap(&mut p1, &mut p2);
15}

We told the compiler, we’re pinning p1. And then we swapped p1 and p2 around. Incredible. Valid? Yes. Right? No.

The simple fix for this is to use the pin! macro. The “trick” it uses is to shadow the original value so we don’t have access to the original variable

 1use std::pin::Pin;
 2use std::marker::PhantomPinned;
 3
 4struct P {
 5    value: u32,
 6    _pin: PhantomPinned,
 7}
 8
 9fn main() {
10    let mut p1 = P { value: 2, _pin: PhantomPinned };
11    let mut p2 = P { value: 4, _pin: PhantomPinned };
12    let p1 = unsafe { Pin::new_unchecked(&mut p1) };
13
14    std::mem::swap(&mut p1, &mut p2); // won't compile
15}
 1error[E0308]: mismatched types
 2   --> src/main.rs:15:26
 3    |
 415  |     std::mem::swap(&mut p1, &mut p2);
 5    |     --------------          ^^^^^^^ expected `&mut Pin<&mut P>`, found `&mut P`
 6    |     |
 7    |     arguments to this function are incorrect
 8    |
 9    = note: expected mutable reference `&mut Pin<&mut P>`
10               found mutable reference `&mut P`