334: Pin and Unpin
Difficulty: 5 Level: Master `Pin<P>` prevents a value from moving in memory โ required for self-referential futures that async state machines create.The Problem This Solves
Rust normally allows moving any value anywhere. `let x = y` moves `y`; passing a value to a function moves it. This is fine for most types โ but not for self-referential types: structs that contain a pointer to their own field. After a move, the pointer points to the old location. That's a dangling pointer and undefined behavior. Why does this matter for async? When you write `async { let x = 1; foo().await; use(x); }`, the compiler generates a state machine struct that, across the `await` point, stores both `x` and potentially a reference to `x` inside the same struct. That's self-referential. Moving this struct after it starts polling would corrupt the internal pointer. `Pin<P>` is a wrapper that statically prevents the value from being moved (unless it implements `Unpin`). `Unpin` is an auto-trait: all "normal" types implement it (`i32`, `String`, `Vec`), meaning they're safe to move. Only types that opt out (by containing `PhantomPinned`) lose `Unpin` and must be accessed through `Pin`.The Intuition
Imagine you've labeled a box and written the box's address on a post-it stuck to the box. If you move the box to a new shelf, the post-it address is now wrong. `Pin` says: "this box is bolted to the shelf โ you can read from it, write to it, but you cannot move it." Most types in Rust (`i32`, `String`, `Vec`) don't have internal pointers and don't care about being moved โ they're `Unpin`. Async state machines and explicitly self-referential structs are the exceptions.How It Works in Rust
use std::marker::PhantomPinned;
use std::pin::Pin;
struct SelfRef {
data: String,
ptr: *const u8, // points into self.data โ invalid after a move!
_pin: PhantomPinned, // removes the auto-Unpin impl
}
impl SelfRef {
fn new(s: &str) -> Pin<Box<Self>> {
let mut b = Box::new(Self {
data: s.to_string(),
ptr: std::ptr::null(),
_pin: PhantomPinned,
});
b.ptr = b.data.as_ptr(); // now points into the heap allocation
// Safety: we never move the value out of the Box after this
unsafe { Pin::new_unchecked(b) }
}
fn ptr_valid(self: Pin<&Self>) -> bool {
self.ptr == self.data.as_ptr() // would fail after a move
}
}
// Normal types are Unpin โ Pin::into_inner() is safe
let mut n = Normal { x: 42 };
let p = Pin::new(&mut n);
let inner = Pin::into_inner(p); // fine: Normal: Unpin
`PhantomPinned` is a zero-sized marker that removes `Unpin`, preventing `Pin::into_inner` and safe movement. Methods that take `Pin<&Self>` (not `&Self`) signal "this requires the pinning guarantee."
What This Unlocks
- Custom `Future` implementations โ correctly implementing `poll` for self-referential state machines.
- Intrusive data structures โ linked lists or trees where nodes hold pointers to neighbors in the same allocation.
- Async generators and coroutines โ any structure that holds a reference into itself across yield points.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Self-referential types | Handled by GC (no move semantics) | Requires `Pin<P>` + `PhantomPinned` |
| Move safety | All values are heap-allocated, GC updates pointers | Stack values can move; programmer must opt in to Pin |
| Unpin | Not applicable | Auto-trait on all safe-to-move types |
| Async futures | Lwt/Effect continuations are always heap-allocated | Rust futures are stack by default, pinned when polled |
//! # Pin and Unpin
//!
//! `Pin<P>` prevents a value from moving in memory โ required for
//! self-referential futures that async state machines create.
use std::marker::PhantomPinned;
use std::pin::Pin;
/// A self-referential struct that contains a pointer to its own field.
/// Moving this struct would invalidate the internal pointer.
pub struct SelfRef {
data: String,
ptr: *const u8,
_pin: PhantomPinned, // Removes the auto-Unpin impl
}
impl SelfRef {
/// Create a new pinned SelfRef. The pointer is set after pinning.
pub fn new(s: &str) -> Pin<Box<Self>> {
let mut boxed = Box::new(Self {
data: s.to_string(),
ptr: std::ptr::null(),
_pin: PhantomPinned,
});
// Set the self-referential pointer
boxed.ptr = boxed.data.as_ptr();
// Safety: we never move the value out of the Box after this
unsafe { Pin::new_unchecked(boxed) }
}
/// Get the data string.
pub fn get_data(&self) -> &str {
&self.data
}
/// Check if the internal pointer is still valid (points to data).
pub fn ptr_valid(&self) -> bool {
self.ptr == self.data.as_ptr()
}
}
/// A normal struct that can be freely moved (implements Unpin).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Normal {
pub x: i32,
}
impl Normal {
pub fn new(x: i32) -> Self {
Self { x }
}
}
/// Demonstrates working with pinned values.
pub fn pin_demo() -> (String, bool) {
let sr = SelfRef::new("hello");
let data = sr.as_ref().get_data().to_string();
let valid = sr.as_ref().ptr_valid();
(data, valid)
}
/// Pin a normal value (Unpin types can be unpinned).
pub fn pin_unpin_demo() -> i32 {
let mut n = Normal::new(42);
let pinned = Pin::new(&mut n);
// Because Normal: Unpin, we can get the inner value back
let inner = Pin::into_inner(pinned);
inner.x
}
/// Check if a type implements Unpin at compile time.
pub fn assert_unpin<T: Unpin>() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_self_ref_data() {
let sr = SelfRef::new("hello");
assert_eq!(sr.as_ref().get_data(), "hello");
}
#[test]
fn test_self_ref_ptr_valid() {
let sr = SelfRef::new("test");
assert!(sr.as_ref().ptr_valid());
}
#[test]
fn test_normal_is_unpin() {
assert_unpin::<Normal>();
assert_unpin::<i32>();
assert_unpin::<String>();
assert_unpin::<Vec<u8>>();
}
#[test]
fn test_pin_into_inner_for_unpin() {
let mut n = Normal::new(100);
let p = Pin::new(&mut n);
let inner = Pin::into_inner(p);
assert_eq!(inner.x, 100);
}
#[test]
fn test_pinned_value_access() {
let mut v = 99i32;
let pv = Pin::new(&mut v);
assert_eq!(*pv, 99);
}
#[test]
fn test_pin_demo() {
let (data, valid) = pin_demo();
assert_eq!(data, "hello");
assert!(valid);
}
}
(* OCaml: self-referential via GC-managed refs *)
type node = { value: int; mutable next: node option }
let make_cycle () =
let n = { value=1; next=None } in
n.next <- Some n; n (* GC handles cycles *)
let with_ref r f = f r
let () =
let data = Array.init 5 (fun i -> i*i) in
with_ref data (fun arr ->
Array.iter (Printf.printf "%d ") arr; print_newline ()
);
Printf.printf "Sum: %d\n" (Array.fold_left (+) 0 data)