首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Pin vs Box:为什么Box不够?

Pin vs Box:为什么Box不够?
EN

Stack Overflow用户
提问于 2022-06-06 10:38:54
回答 1查看 1.3K关注 0票数 10

我想知道将T类型保存在Box中是不安全的,而在Pin中则是安全的。

最初,我认为std::marker::PhantomPinned可以阻止实例被移动(通过禁止它),但表面上没有。因为:

代码语言:javascript
复制
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);
}

这段代码是可编译的,尽管它将MyStructmain移动到func等等。

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

因此,如果有人就这些问题详细阐述这一专题,我将不胜感激。由于在其他问题和文档中没有涉及到这个问题,所以仅仅通过Box就能解决问题。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-06-06 12:23:33

我觉得你误会了。

PhantomPinned而不是使数据不可移动。它只是说一旦数据固定,它就再也不能被解除了。

因此,要使使用PhantomPinned的数据不可移动,您必须首先使用Pin

例如,如果创建MyStruct变量的固定版本,则无法解锁它:

代码语言:javascript
复制
fn main() {
    let pinned_x = Box::pin(MyStruct::new(5));
    let unpinned_x = Pin::into_inner(pinned_x);
}
代码语言:javascript
复制
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`

使用普通的结构,您可以在没有问题的情况下解开它:

代码语言:javascript
复制
struct MyUnpinnableStruct;

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

PinBox的区别

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

正如您从前面的示例中可以看到的那样,这两种方法通常都是结合使用的,因为防止某物移动的最简单方法是将其放到堆中。

PhantomPin使类成为!Unpin,这意味着一旦它们被固定,它们就不能再被解除固定。

您可以尝试对堆栈上的值使用Pin,但很快就会遇到问题。当它适用于可解锁的结构时:

代码语言:javascript
复制
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的结构,它失败。

代码语言:javascript
复制
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);
}
代码语言:javascript
复制
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`

BoxPin

虽然Box的内容在堆上,因此具有一个常量地址,但仍然可以将其从堆移回堆栈,这在Pin对象中是不可能的:

代码语言:javascript
复制
// 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();
}
代码语言:javascript
复制
Address: 0x557452040ad0
Address: 0x7ffde8f7f0d4

实施Pin

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

代码语言:javascript
复制
fn print_addr(self: Pin<&Self>)

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

代码语言:javascript
复制
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才真正有用。如果没有unsafe代码,借用检查器就足以跟踪对象。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72516441

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档