๐Ÿฆ€ Functional Rust

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

Key Differences

ConceptOCamlRust
Self-referential typesHandled by GC (no move semantics)Requires `Pin<P>` + `PhantomPinned`
Move safetyAll values are heap-allocated, GC updates pointersStack values can move; programmer must opt in to Pin
UnpinNot applicableAuto-trait on all safe-to-move types
Async futuresLwt/Effect continuations are always heap-allocatedRust 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)