ExamplesBy LevelBy TopicLearning Paths
334 Advanced

334: Pin and Unpin

Functional Programming

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

  • • Understand that Pin<P> prevents the pointed-to value from being moved
  • • Recognize Unpin as a marker trait for types safe to move even when pinned
  • • Use PhantomPinned to opt out of the automatic Unpin implementation
  • • Understand why Future::poll takes Pin<&mut Self> — futures may be self-referential
  • Code 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

  • GC eliminates the need: OCaml's GC tracks all pointers and updates them on object movement — the problem Pin solves doesn't arise.
  • Zero-cost: Rust's 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.
  • In practice: You rarely implement 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);
        }
    }
    ✓ Tests Rust test suite
    #[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

    AspectOCamlRust
    Memory managementGCManual / ownership
    Self-ref safetyAutomaticRequires Pin
    Move semanticsCopy by defaultMove by default
    Unpin equivalentN/AAuto-trait

    Exercises

  • Show that a normal struct (without PhantomPinned) is automatically Unpin by checking assert_impl_all!(MyStruct: Unpin).
  • Implement a SelfRef correctly and verify that the internal pointer remains valid after the initial setup.
  • Write a minimal custom Future for a self-referential state machine and implement poll() taking Pin<&mut Self>.
  • Open Source Repos