Reputation: 2368
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:
boxed
and twos
, hence drop()
will be called on those when they go out of scope.drop()
does not need to be manually implemented for Foo
.fives
exists on the heap and twos
exists on the stack.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
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