Pin<Ptr> and Drop

A key challenge with pinned, !Unpin types is implementing the Drop trait. The drop method takes &mut self, which allows moving the value. However, pinned values must not be moved.

An Incorrect Drop Implementation

It’s easy to accidentally move a value inside drop. Operations like assignment, ptr::read, and mem::replace can silently break the pinning guarantee.

struct SelfRef {
    data: String,
    ptr: *const String,
}

impl Drop for SelfRef {
    fn drop(&mut self) {
        // BAD: `ptr::read` moves `self.data` out of `self`.
        // When `_dupe` is dropped at the end of the function, it's a double free!
        let _dupe = unsafe { std::ptr::read(&self.data) };
    }
}
This slide and its sub-slides should take about 15 minutes. `!Unpin` types can make it difficult to safely implement `Drop`. Implementations must not move pinned values.

Pinned types make guarantees about memory stability. Operations like ptr::read and mem::replace can silently break these guarantees by moving or duplicating data, invalidating internal pointers without the type system’s knowledge.

In this drop() method, _dupe is a bitwise copy of self.data. At the end of the method, it will be dropped along with self. This double drop is undefined behavior.

A Correct Drop Implementation

To implement Drop correctly for a !Unpin type, you must ensure that the value is not moved. A common pattern is to create a helper function that operates on Pin<&mut T>.

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

struct SelfRef {
    data: String,
    ptr: *const String,
    _pin: PhantomPinned,
}

impl SelfRef {
    fn new(data: impl Into<String>) -> Pin<Box<SelfRef>> {
        let mut this = Box::pin(SelfRef {
            data: data.into(),
            ptr: std::ptr::null(),
            _pin: PhantomPinned,
        });
        let ptr: *const String = &this.data;
        // SAFETY: `this` is pinned before we create the self-reference.
        unsafe {
            Pin::as_mut(&mut this).get_unchecked_mut().ptr = ptr;
        }
        this
    }

    // This function can only be called on a pinned `SelfRef`.
    unsafe fn drop_pinned(self: Pin<&mut SelfRef>) {
        // `self` is pinned, so we must not move out of it.
        println!("dropping {}", self.data);
    }
}

impl Drop for SelfRef {
    fn drop(&mut self) {
        // We can safely call `drop_pinned` because `drop` is the last time
        // the value is used. We use `new_unchecked` because we know `self`
        // will not be moved again.
        unsafe {
            SelfRef::drop_pinned(Pin::new_unchecked(self));
        }
    }
}

fn main() {
    let _pinned = SelfRef::new("Hello, ");
} // `Drop` runs without moving the pinned value