Why we use `Box::pin` for !Unpin and `Pin::new` for Unpin?

Viewed 648

I get confused when learning implementation of Pin in rust.

I understand when a type is not safe to move, it can impl !Unpin. Pin is to prevent others get &mut T from these types, so that they can not be std::mem::swap.

If Pin is designed for !Unpin types, why we can't just call Pin::new on those types?

It gives error like the following. I know I should use Pin::new_unchecked, but why?

struct TestNUnpin {
    b: String,
}
impl !Unpin for TestNUnpin {}

// error: the trait `Unpin` is not implemented for `TestNUnpin`
std::pin::Pin::new(&TestNUnpin{b: "b".to_owned()});

My reasoning is:

  1. Pin is to help !Unpin types
  2. We can pass !Unpin types to Pin::new to make them unmovable.
  3. For Unpin types, they can not be pinned, so we can't created by Pin::new
1 Answers

I think what you're looking for can be found in the Safety section of Pin::new_unchecked. Essentially, Pin should guarantee that the pinned value will never move again (unless it implements Unpin), even after the Pin is dropped. An example of this failing is Pin<&mut T>. You can drop the Pin and the value is no longer borrowed, so you're free to move it again, breaking Pin's core guarantee. Here's an example:

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

fn main() {
    let x = PhantomPinned;
    
    {
        let _pin = Pin::new(&x); // error[E0277]: `PhantomPinned` cannot be unpinned
    }

    let y = Box::new(x); // the PhantomPinned is moved here!
}

This check simply isn't doable at compile-time without adding a whole lot of extra complexity to the borrow checker, so it's marked as unsafe, essentially saying it's the developer's job to make sure it works. The reason Box::pin exists and is safe is because the developers of Box can guarantee its safety: Box is an owned and unique pointer, so once its Pin is dropped, its value is also dropped, and there's no longer any way to move the value.

Related