Pin 与 Box:为什么 Box 还不够?

dro*_*te7 17 rust

我想知道一些例子,其中将T类型保留在其中Box是不安全的,而保留在其中Pin则是安全的。

最初,我认为这std::marker::PhantomPinned可以防止实例被移动(通过禁止它),但似乎事实并非如此。自从:

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);
}
Run Code Online (Sandbox Code Playgroud)

该代码是可编译的,尽管它MyStructmainfunc等移动。

至于BoxPin它们都将其内容保留在堆上,因此它似乎不受动议的影响。

因此,如果有人就这些问题详细阐述这个主题,我将不胜感激。由于其他问题和文档中没有涵盖它,因此仅仅使用Box.

Fin*_*nis 21

我认为你误会了。

PhantomPinned不会使数据不可移动。它只是说,一旦数据固定,就永远无法再次取消固定。

因此,要使数据PhantomPinned不可移动,首先必须做到Pin这一点。

例如,如果您创建MyStruct变量的固定版本,则无法取消固定它:

fn main() {
    let pinned_x = Box::pin(MyStruct::new(5));
    let unpinned_x = Pin::into_inner(pinned_x);
}
Run Code Online (Sandbox Code Playgroud)
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`
Run Code Online (Sandbox Code Playgroud)

使用普通结构时,您可以毫无问题地取消固定它:

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`
Run Code Online (Sandbox Code Playgroud)

Pin和之间的区别Box

它们是完全不同的概念。Pin确保它指向的数据不能被移动。Box将一些东西放在堆上。

正如您从前面的示例中看到的,两者经常结合使用,因为防止某些内容移动的最简单方法是将其放在堆上。

PhantomPin导致类成为!Unpin,这意味着一旦它们被固定,就不能再取消固定。

您可以尝试使用Pin堆栈上的值,但很快就会遇到问题。虽然它适用于不可固定的结构:

struct MyUnpinnableStruct;

fn main() {
    let pinned_x = Box::pin(MyUnpinnableStruct);
    let unpinned_x = Pin::into_inner(pinned_x);
}
Run Code Online (Sandbox Code Playgroud)

对于包含以下内容的结构失败PhantomPinned

struct MyUnpinnableStruct(u32);

fn main() {
    let y = MyUnpinnableStruct(7);
    {
        let pinned_y = Pin::new(&y);
    }
    // This moves y into the `drop` function
    drop(y);
}
Run Code Online (Sandbox Code Playgroud)
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`
Run Code Online (Sandbox Code Playgroud)

更新: Rust1.68.0引入了std::pin::pin!()用于将项目固定在堆栈上。它的工作原理是获取所有权并隐藏固定的对象,使得在删除Pin.


Box没有Pin

虽然 的内容Box位于堆上,因此具有恒定的地址,但您仍然可以将其从堆移回堆栈,这对于对象来说是不可能的Pin

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);
}
Run Code Online (Sandbox Code Playgroud)
Address: 0x557452040ad0
Address: 0x7ffde8f7f0d4
Run Code Online (Sandbox Code Playgroud)

执行Pin

要确保对象固定在成员函数中,可以使用以下语法:

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`
Run Code Online (Sandbox Code Playgroud)

与 一起PhantomPinned,您现在可以 100% 确定始终为同一对象打印相同的地址print_addr

// 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();
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,绝对无法再次取消固定x_pinnedprint_addr只能在固定对象上调用。

为什么这有用?例如,因为您现在可以按照Future特征中的要求使用原始指针。

但总的来说,Pin只有与代码配合使用才真正有用unsafe。没有unsafe代码,借用检查器足以跟踪您的对象。