Why does &i32 sometimes implement Unpin and sometimes not?

Viewed 38
use std::pin::Pin;

fn main() {
    let val: i32 = 3;

    let val_pin = Pin::new(&val);
    f(val_pin);

    f(Pin::new(&val));
}

fn f(_val: Pin<&dyn TestTrait>) {}

trait TestTrait {}

impl TestTrait for i32 {}

Line 9 f(Pin::new(&val)); throws a compiler error, but line 6 let val_pin = Pin::new(&val); does not. The compiler error is:

error[E0277]: `dyn TestTrait` cannot be unpinned
   --> src/main.rs:9:7
    |
9   |     f(Pin::new(&val));
    |       ^^^^^^^^ the trait `Unpin` is not implemented for `dyn TestTrait`
    |
    = note: consider using `Box::pin`
note: required by a bound in `Pin::<P>::new`
   --> /usr/lib/rustlib/src/rust/library/core/src/pin.rs:482:23
    |
482 | impl<P: Deref<Target: Unpin>> Pin<P> {
    |                       ^^^^^ required by this bound in `Pin::<P>::new`

So it seems that the trait Unpin is implemented for &val in line 6, but not in line 9. Why is that?

1 Answers

This has to do with some subtleties of type inference.

The variable on line 6 is inferred to be Pin<&i32> which later gets automatically upcast to Pin<&dyn TestTrait> on line 7. You can check this by inserting the following assignment after line 6, which forces a compile-time error that shows you the type of my_val:

let _: () = my_val;
  = note: expected unit type `()`
                found struct `Pin<&i32>`

(Side note: this won't work if my_val has type () -- then the assignment will succeed!)

In contrast, when you create the Pin as a temporary on line 9, it is inferred to be Pin<&dyn TestTrait> instead of Pin<&i32>, and &dyn TestTrait doesn't implement Unpin by default.

You can fix this by explicitly specifying the type argument T of Pin<T> to be &i32 on line 9:

f(Pin::<&i32>::new(&val));

You could also fix this by specifying a bound on TypeTrait of Unpin, which will cause &dyn TestTrait to implement Unpin as well:

trait TestTrait: Unpin {}

Finally, you could also fix the issue by changing f to accept Pin<&impl TestTrait> instead of Pin<&dyn TestTrait>:

fn f(_val: Pin<&impl TestTrait>) {}

As pointed out by @KevinReid, you can also indicate that the trait object must implement Unpin, which is allowed because Unpin has no methods:

fn f(_val: Pin<&(dyn TestTrait + Unpin)>) {}
Related