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

问题描述 投票:0回答:1

我想知道一些例子,其中将

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

此代码是可编译的,尽管它从

MyStruct
移动到
main
等等。
至于

func

Box
,它们都将其内容保留在堆上,因此它似乎不受动议影响。
因此,如果有人就这些问题详细阐述这个主题,我将不胜感激。由于其他问题和文档中没有涵盖它,因此仅仅通过 

Pin

来解决本质上是错误的。

    

rust
1个回答
20
投票

Box

使数据不可移动。它只是说,一旦数据固定,它将永远无法再次取消固定。 因此,要使

PhantomPinned

的数据不可移动,必须先

PhantomPinned
例如,如果您创建 

Pin

变量的固定版本,则无法取消固定它:

MyStruct

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`


struct MyUnpinnableStruct; fn main() { let pinned_x = Box::pin(MyUnpinnableStruct); let unpinned_x = Pin::into_inner(pinned_x); }

Pin
之间的区别
它们是完全不同的概念。

Box

确保它指向的数据无法移动。

Pin
将一些东西放入堆中。
从前面的示例中可以看出,两者经常结合使用,因为防止某些内容移动的最简单方法是将其放在堆上。

Box

导致类变为

PhantomPin
,这意味着一旦它们被固定,就无法再取消固定。
您可以尝试对堆栈上的值使用

!Unpin

,但您很快就会遇到问题。虽然它适用于不可固定的结构:

Pin

对于包含 
struct MyUnpinnableStruct(u32); fn main() { let y = MyUnpinnableStruct(7); { let pinned_y = Pin::new(&y); } // This moves y into the `drop` function drop(y); }

的结构失败:

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);
}
更新:

Rust 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` 引入了

1.68.0
,它
可以
用于将项目固定在堆栈上。它的工作原理是获取所有权并隐藏固定的对象,使得在删除std::pin::pin!()后无法再次访问它。


Pin

没有
Box
虽然 

Pin

的内容位于堆上,因此具有恒定的地址,但您仍然可以将其从堆移回到堆栈,而这对于

Box
对象来说是不可能的:
Pin

// 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

fn print_addr(self: Pin<&Self>)

一起,您现在可以 100% 确定

PhantomPinned
始终
为同一对象打印相同的地址: print_addr

在此示例中,绝对无法再次取消固定 
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(); }

,并且只能在固定的对象上调用

x_pinned
为什么这很有用?例如,因为您现在可以使用原始指针,如 

print_addr

特征中所要求的。

但一般来说,

Future

仅在与

Pin
代码配对时才真正有用。没有
unsafe
代码,借用检查器足以跟踪您的对象。
    

© www.soinside.com 2019 - 2024. All rights reserved.