HiDefender
HiDefender

Reputation: 2368

Box::pin() vs Pin::new_unchecked()

Trying to understand the difference between Box::pin() and Pin::new_unchecked(). Suppose the following code:

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

struct Foo {
    x: i32,
    _pin: PhantomPinned,
}

fn bar() {

    let fives = Foo {
        x: 5,
        _pin: PhantomPinned,
    };
    let mut boxed = Box::pin(fives);
    unsafe {
        let mut_ref: Pin<&mut Foo> = Pin::as_mut(&mut boxed);
        Pin::get_unchecked_mut(mut_ref).x = 55;
    }
    println!("fives: {}", boxed.x);
    // fives.x = 555; //Won't compile Box::pin() consumed fives.
    
    let mut twos = Foo {
        x: 2,
        _pin: PhantomPinned,
    };
    let mut ptr = unsafe{ Pin::new_unchecked(&mut twos) };
    unsafe {
        let mut_ref: Pin<&mut Foo> = Pin::as_mut(&mut ptr);
        Pin::get_unchecked_mut(mut_ref).x = 22;
    }
    println!("twos: {}", twos.x);
    twos.x = 222;
    println!("twos: {}", twos.x);
}

fn main() {
    bar();
}

My understanding is that:

Is this correct? When is Box::pin() appropriate and when is Pin::new_unchecked() appropriate? When does drop() need to be implemented for a !Unpin struct?

Upvotes: 1

Views: 1228

Answers (1)

Peter Hall
Peter Hall

Reputation: 58725

Box::pin is always safe to use. The boxed value is already owned by the box, and moving it into a Pin guarantees that there are no other references to it at that time because the borrow checker would not otherwise allow the move to happen. The contents can only be unpinned if it implements Unpin which is enforced by the type checker.

Pin::new_unchecked is not always safe to use. It takes an arbitrary pointer type, not just a box, and there could be other copies of that pointer. Because the pointer could be anything, the compiler cannot provide guarantees that the data that it points to won't be moved by some other code without being unpinned first. When you use Pin::new_unchecked it's up to you make sure that you enforce all assumptions that a user of the resulting Pin would expect. If you do not then it could trigger Undefined Behaviour.

As it happens, Pin::new_unchecked is always safe to use with immutable &-references and Box. It is however not safe to use with mutable &mut-references Rc, Arc or raw pointers, unless you independently make sure that all of the pin guarantees are enforced. There is detailed information about safety of Pin::new_unchecked in its documentation.

The documentation provides this example of how Pin::new_unchecked can cause UB with an &mut reference:

use std::mem;
use std::pin::Pin;

fn move_pinned_ref<T>(mut a: T, mut b: T) {
    unsafe {
        let p: Pin<&mut T> = Pin::new_unchecked(&mut a);
        // This should mean the pointee `a` can never move again.
    }
    mem::swap(&mut a, &mut b);
    // The address of `a` changed to `b`'s stack slot, so `a` got moved even
    // though we have previously pinned it! We have violated the pinning API contract.
}

Upvotes: 3

Related Questions