我想知道一些例子,其中将
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
来解决本质上是错误的。
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
代码,借用检查器足以跟踪您的对象。