Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pin vs Box: Why is Box not enough?

Tags:

rust

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.

like image 995
dronte7 Avatar asked Sep 11 '25 04:09

dronte7


1 Answers

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);
}

Difference between 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

Enforcing 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.

like image 188
Finomnis Avatar answered Sep 13 '25 20:09

Finomnis