334: Pin and Unpin
Tutorial Video
Text description (accessibility)
This video demonstrates the "334: Pin and Unpin" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. Async state machines generated by `async fn` are self-referential: they contain pointers to their own fields (to hold references across `.await` points). Key difference from OCaml: 1. **GC eliminates the need**: OCaml's GC tracks all pointers and updates them on object movement — the problem `Pin` solves doesn't arise.
Tutorial
The Problem
Async state machines generated by async fn are self-referential: they contain pointers to their own fields (to hold references across .await points). Moving a self-referential struct would invalidate its internal pointers — a memory safety violation. Pin<P> prevents such movement. Unpin marks types that are safe to move even when pinned. Understanding Pin/Unpin is essential for implementing custom Future types and understanding why .await requires Pin<&mut Future>.
🎯 Learning Outcomes
Pin<P> prevents the pointed-to value from being movedUnpin as a marker trait for types safe to move even when pinnedPhantomPinned to opt out of the automatic Unpin implementationFuture::poll takes Pin<&mut Self> — futures may be self-referentialCode Example
struct SelfRef {
data: String,
ptr: *const u8, // Raw pointer into data
_pin: PhantomPinned, // Prevents Unpin
}
impl SelfRef {
fn new(s: &str) -> Pin<Box<Self>> {
let mut b = Box::new(Self { ... });
b.ptr = b.data.as_ptr();
unsafe { Pin::new_unchecked(b) }
}
}Key Differences
Pin solves doesn't arise.Pin is a zero-cost abstraction — it adds no runtime overhead, only compile-time restrictions.Unpin as escape hatch**: Most types are Unpin by default (i32, String, Vec) — Pin only restricts !Unpin types.Pin manually; Box::pin(future) and pin_mut!(local) (from pin-utils) handle most cases.OCaml Approach
OCaml's garbage collector handles self-referential data transparently — GC knows about all pointers and updates them if objects move. OCaml does not need an equivalent of Pin:
(* OCaml: self-referential structures work naturally *)
type node = { mutable next: node option; value: int }
let n = { next = None; value = 42 }
n.next <- Some n (* self-referential: fine in OCaml *)
Full Source
#![allow(clippy::all)]
//! # 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);
}
}#[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);
}
}
Deep Comparison
OCaml vs Rust: Pin and Unpin
Self-Referential Struct
OCaml (no pinning needed):
type self_ref = {
data: string;
mutable ptr: int option; (* Index, not pointer *)
}
(* GC handles memory, no concern about moves *)
Rust:
struct SelfRef {
data: String,
ptr: *const u8, // Raw pointer into data
_pin: PhantomPinned, // Prevents Unpin
}
impl SelfRef {
fn new(s: &str) -> Pin<Box<Self>> {
let mut b = Box::new(Self { ... });
b.ptr = b.data.as_ptr();
unsafe { Pin::new_unchecked(b) }
}
}
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Memory management | GC | Manual / ownership |
| Self-ref safety | Automatic | Requires Pin |
| Move semantics | Copy by default | Move by default |
Unpin equivalent | N/A | Auto-trait |
Exercises
PhantomPinned) is automatically Unpin by checking assert_impl_all!(MyStruct: Unpin).SelfRef correctly and verify that the internal pointer remains valid after the initial setup.Future for a self-referential state machine and implement poll() taking Pin<&mut Self>.