Introduction

Some data structures contain pointers to themselves. If such a struct is moved in memory, its internal pointers become dangling. Pin is Rust's solution for guaranteeing that a value stays at its current memory address, making self-referential types safe.

Key Concepts

  • Self-referential struct: A struct that contains a pointer to one of its own fields. Moving the struct invalidates the pointer.
  • Pin<P>: A wrapper around a pointer P that guarantees the pointee will not be moved out.
  • PhantomPinned: A marker type that opts a struct out of the Unpin trait, making it truly immovable once pinned.

Real World Context

Async/await in Rust desugars into state machines that may contain self-references — a local variable and a reference to it stored across an await point. Without Pin, the async runtime could move the Future and corrupt these internal references. Pin is what makes the entire async ecosystem safe.

Deep Dive

The core problem is straightforward. If a struct holds a pointer to itself, moving the struct breaks the pointer:

rust
struct SelfRef {
    value: String,
    ptr: *const String,  // Points to value above
}

let mut s = SelfRef {
    value: String::from("hello"),
    ptr: std::ptr::null(),
};
s.ptr = &s.value;  // ptr now points to value's address

// If we move s...
let s2 = s;  // value moves to new address!
// s2.ptr still points to the OLD address — dangling!

Async functions create exactly this pattern. The state machine generated by the compiler stores both local variables and references to them:

rust
async fn example() {
    let data = vec![1, 2, 3];
    let reference = &data[0];  // Self-reference within the state machine
    some_async_op().await;     // Suspension point — state is saved
    println!("{reference}");   // reference must still be valid
}

Pin<P> guarantees the pointee won't move:

rust
use std::pin::Pin;
use std::marker::PhantomPinned;

struct Unmovable {
    data: String,
    _pin: PhantomPinned,  // Makes type !Unpin
}

let pinned: Pin<Box<Unmovable>> = Box::pin(Unmovable {
    data: String::from("hello"),
    _pin: PhantomPinned,
});

// Can't move out of pinned!
// let moved = *pinned;  // Error!

Common Pitfalls

  1. Assuming all types need Pin — Most types have no self-references and implement Unpin, meaning Pin has no effect on them. Only types with internal pointers to themselves need pinning.
  2. Trying to create Pin on the stack without the pin! macro — Before Rust 1.68's pin! macro, stack pinning required unsafe code. Use pin! or Box::pin for safe pinning.

Best Practices

  1. Use Box::pin for heap pinning — It is the simplest and safest way to create a pinned value.
  2. Use the pin-project crate for custom futures — It provides safe pin projection macros that eliminate the need for unsafe code.

Summary

  • Self-referential structs break if moved because internal pointers become dangling.
  • Pin<P> guarantees the pointee won't move, making self-references safe.
  • Async functions generate self-referential state machines, which is why Futures require pinning.
  • PhantomPinned opts a type out of Unpin, making it truly immovable once pinned.

Code Examples

rust
use std::pin::Pin;
use std::marker::PhantomPinned;

struct SelfReferential {
    data: String,
    slice: *const str,  // Will point into data
    _pin: PhantomPinned,
}

impl SelfReferential {
    fn new(data: String) -> Pin<Box<Self>> {
        let res = Self {
            data,
            slice: std::ptr::null(),
            _pin: PhantomPinned,
        };
        let mut boxed = Box::pin(res);
        
        // Safe: we're setting up the self-reference
        // before anyone can observe it
        let slice: *const str = &boxed.data;
        unsafe {
            let mut_ref = Pin::as_mut(&mut boxed);
            Pin::get_unchecked_mut(mut_ref).slice = slice;
        }
        
        boxed
    }
    
    fn get_slice(self: Pin<&Self>) -> &str {
        unsafe { &*self.slice }
    }
}
✓ Completed