I would like to know examples where keeping a T
type within Box
would be unsafe, while within Pin
it would be safe.
Initially, I thought that std::marker::PhantomPinned
prevents an instance from being moved around (by forbidding it), but seemingly it does not. Since:
use std::pin::Pin;
use std::marker::PhantomPinned;
#[derive(Debug)]
struct MyStruct {
field: u32,
_pin: PhantomPinned
}
impl MyStruct {
fn new(field: u32) -> Self {
Self {
field,
_pin: PhantomPinned,
}
}
}
fn func(x: MyStruct) {
println!("{:?}", x);
func2(x);
}
fn func2(x: MyStruct) {
println!("{:?}", x);
}
fn main() {
let x = MyStruct::new(5);
func(x);
}
this code is compilable, despite the fact that it moves MyStruct
from main
to func
and etc.
as for Box
and Pin
they both keep their contents on the heap, so it does not seem to be subjected to motions.
Thus, I would appreciate if someone elaborated this topic on these questions. Since it is not covered in other questions and docs, what is inherently wrong with just getting by with Box
.
I think you misunderstand.
PhantomPinned
does not make data immovable. It just says that once the data is pinned, it will never be able to be unpinned again.
Therefore, to make data with PhantomPinned
unmovable, you have to Pin
it first.
For example, if you create a pinned version of your MyStruct
variable, you cannot unpin it:
fn main() {
let pinned_x = Box::pin(MyStruct::new(5));
let unpinned_x = Pin::into_inner(pinned_x);
}
error[E0277]: `PhantomPinned` cannot be unpinned
--> src/main.rs:20:38
|
20 | let unpinned_x = Pin::into_inner(pinned_x);
| --------------- ^^^^^^^^ within `MyStruct`, the trait `Unpin` is not implemented for `PhantomPinned`
| |
| required by a bound introduced by this call
|
= note: consider using `Box::pin`
note: required because it appears within the type `MyStruct`
--> src/main.rs:4:8
|
4 | struct MyStruct {
| ^^^^^^^^
note: required by a bound in `Pin::<P>::into_inner`
--> /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/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>::into_inner`
While with a normal struct, you can unpin it without a problem:
struct MyUnpinnableStruct;
fn main() {
let pinned_x = Box::pin(MyUnpinnableStruct);
let unpinned_x = Pin::into_inner(pinned_x);
}
Pin
and Box
They are both completely different concepts. Pin
makes sure that the data it points to cannot be moved. Box
puts something on the heap.
As you can see from the previous examples, both are often used in conjunction, as the easiest way to prevent something from moving is to put it on the heap.
PhantomPin
causes classes to be !Unpin
, meaning once they are pinned, they can no longer be unpinned.
You can try to use Pin
on values on the stack, but you will run into problems quickly. While it works for unpin-able structs:
struct MyUnpinnableStruct(u32);
fn main() {
let y = MyUnpinnableStruct(7);
{
let pinned_y = Pin::new(&y);
}
// This moves y into the `drop` function
drop(y);
}
It fails for structs that contain PhantomPinned
:
fn main() {
let x = MyStruct::new(5);
{
// This fails; pinning a reference to a stack object
// will fail, because once we drop that reference the
// object will be movable again. So we cannot `Pin` stack objects
let pinned_x = Pin::new(&x);
}
// This moves x into the `drop` function
drop(x);
}
error[E0277]: `PhantomPinned` cannot be unpinned
--> src/main.rs:24:33
|
24 | let pinned_x = Pin::new(&x);
| -------- ^^ within `MyStruct`, the trait `Unpin` is not implemented for `PhantomPinned`
| |
| required by a bound introduced by this call
|
= note: consider using `Box::pin`
note: required because it appears within the type `MyStruct`
--> src/main.rs:4:8
|
4 | struct MyStruct {
| ^^^^^^^^
note: required by a bound in `Pin::<P>::new`
--> /home/martin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/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`
Update: Rust 1.68.0
introduced std::pin::pin!()
which can be used to pin items on the stack. It works by taking ownership and hiding the pinned object, making it impossible to access it again after dropping the Pin
.
Box
without Pin
While the content of Box
is on the heap and therefore has a constant address, you can still move it back from the heap to the stack, which wouldn't be possible with a Pin
object:
// Note that MyData does not implement Clone or Copy
struct MyData(u32);
impl MyData {
fn print_addr(&self) {
println!("Address: {:p}", self);
}
}
fn main() {
// On the heap
let x_heap = Box::new(MyData(42));
x_heap.print_addr();
// Moved back on the stack
let x_stack = *x_heap;
x_stack.print_addr();
}
Address: 0x557452040ad0
Address: 0x7ffde8f7f0d4
Pin
To make sure that an object is pinned in a member function, you can use the following syntax:
fn print_addr(self: Pin<&Self>)
Together with PhantomPinned
, you now can be 100% sure that print_addr
will always print the same address for the same object:
use std::{marker::PhantomPinned, pin::Pin};
struct MyData(u32, PhantomPinned);
impl MyData {
fn print_addr(self: Pin<&Self>) {
println!("Address: {:p}", self);
}
}
fn main() {
// On the heap
let x_pinned = Box::pin(MyData(42, PhantomPinned));
x_pinned.as_ref().print_addr();
// Moved back on the stack
let x_unpinned = Pin::into_inner(x_pinned); // FAILS!
let x_stack = *x_unpinned;
let x_pinned_again = Box::pin(x_stack);
x_pinned_again.as_ref().print_addr();
}
In this example, there is absolutely no way to ever unpin x_pinned
again, and print_addr
can only be called on the pinned object.
Why is this useful? For example because you can now work with raw pointers, as is required in the Future
trait.
But in general, Pin
is only really useful if paired with unsafe
code. Without unsafe
code, the borrow checker is sufficient to keep track of your objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With