rust放置木箱转向_【译文】Rust异步编程: Pinning

译者注:如果你一时半会没啃动Pinning,也别心急,试试阅读这篇《Rust的Pin与Unpin - Folyd》,理解起来会容易不少。

Pinning详解

让我们尝试使用一个比较简单的示例来了解pinning。前面我们遇到的问题,最终可以归结为如何在Rust中处理自引用类型的引用的问题。

现在,我们的示例如下所示:

usestd::pin::Pin;​#[derive(Debug)]struct Test{a: String,b: *constString,}​implTest{fn new(txt: &str)-> Self{Test{a: String::from(txt),b: std::ptr::null(),}}​fn init(&mutself){letself_ref: *constString=&self.a;self.b=self_ref;}​fn a(&self)-> &str {&self.a}​fn b(&self)-> &String {unsafe{&*(self.b)}}}

Test提供了获取字段a和b值引用的方法。由于b是对a的引用,因此我们将其存储为指针,因为Rust的借用规则不允许我们定义这种生命周期。现在,我们有了所谓的自引用结构。

如果我们不移动任何数据,则该示例运行良好,可以通过运行示例观察:

fn main(){letmuttest1=Test::new("test1");test1.init();letmuttest2=Test::new("test2");test2.init();​println!("a: {}, b: {}",test1.a(),test1.b());println!("a: {}, b: {}",test2.a(),test2.b());​}

我们得到了我们期望的结果:

a: test1, b: test1

a: test2, b: test2

让我们看看如果将test1与test2交换导致数据移动会发生什么:

fn main(){letmuttest1=Test::new("test1");test1.init();letmuttest2=Test::new("test2");test2.init();​println!("a: {}, b: {}",test1.a(),test1.b());std::mem::swap(&muttest1,&muttest2);println!("a: {}, b: {}",test2.a(),test2.b());​}

我们天真的以为应该两次获得test1的调试打印,如下所示:

a: test1, b: test1

a: test1, b: test1

但我们得到的是:

a: test1, b: test1

a: test1, b: test2

test2.b的指针仍然指向了原来的位置,也就是现在的test1的里面。该结构不再是自引用的,它拥有一个指向不同对象字段的指针。这意味着我们不能再依赖test2.b的生命周期和test2的生命周期的绑定假设了。

如果您仍然不确定,那么下面可以让您确定了吧:

fn main(){letmuttest1=Test::new("test1");test1.init();letmuttest2=Test::new("test2");test2.init();​println!("a: {}, b: {}",test1.a(),test1.b());std::mem::swap(&muttest1,&muttest2);test1.a="I've totally changed now!".to_string();println!("a: {}, b: {}",test2.a(),test2.b());​}

下图可以帮助您直观地了解正在发生的事情:

这很容易使它展现出未定义的行为并“壮观地”失败。

Pinning实践

让我们看下Pinning和Pin类型如何帮助我们解决此问题。

Pin类型封装了指针类型,它保证不会移动指针后面的值。例如,Pin,Pin,Pin>都保证T不被移动,当且仅当T:!Unpin。

大多数类型在移动时都没有问题。这些类型实现了Unpin特型。可以将Unpin类型的指针自由的放置到Pin中或从中取出。例如,u8是Unpin,因此Pin的行为就像普通的&mut u8。

但是,固定后无法移动的类型具有一个标记为!Unpin的标记。由async / await创建的Futures就是一个例子。

栈上固定

回到我们的例子。我们可以使用Pin来解决我们的问题。让我们看一下我们的示例的样子,我们需要一个pinned的指针:

usestd::pin::Pin;usestd::marker::PhantomPinned;​#[derive(Debug)]struct Test{a: String,b: *constString,_marker: PhantomPinned,}​​implTest{fn new(txt: &str)-> Self{Test{a: String::from(txt),b: std::ptr::null(),_marker: PhantomPinned,// This makes our type `!Unpin`}}fn init(self: Pin){letself_ptr: *constString=&self.a;letthis=unsafe{self.get_unchecked_mut()};this.b=self_ptr;}​fn a(self: Pin)-> &'astr{&self.get_ref().a}​fn b(self: Pin)-> &'aString{unsafe{&*(self.b)}}}

如果我们的类型实现!Unpin,则将对象固定到栈始终是不安全的。您可以使用诸如test1和test2固定到栈上:

pubfn main(){// test1 is safe to move before we initialize itletmuttest1=Test::new("test1");// Notice how we shadow `test1` to prevent it from being accessed againletmuttest1=unsafe{Pin::new_unchecked(&muttest1)};Test::init(test1.as_mut());​letmuttest2=Test::new("test2");letmuttest2=unsafe{Pin::new_unchecked(&muttest2)};Test::init(test2.as_mut());​println!("a: {}, b: {}",Test::a(test1.as_ref()),Test::b(test1.as_ref()));println!("a: {}, b: {}",Test::a(test2.as_ref()),Test::b(test2.as_ref()));}

如果现在尝试移动数据,则会出现编译错误:

pubfn main(){letmuttest1=Test::new("test1");letmuttest1=unsafe{Pin::new_unchecked(&muttest1)};Test::init(test1.as_mut());​letmuttest2=Test::new("test2");letmuttest2=unsafe{Pin::new_unchecked(&muttest2)};Test::init(test2.as_mut());​println!("a: {}, b: {}",Test::a(test1.as_ref()),Test::b(test1.as_ref()));std::mem::swap(test1.get_mut(),test2.get_mut());println!("a: {}, b: {}",Test::a(test2.as_ref()),Test::b(test2.as_ref()));}

类型系统阻止我们移动数据。

需要注意,栈固定将始终依赖于您在编写unsafe时提供的保证。虽然我们知道&'a mut T所指的对象在生命周期'a中固定,但我们不知道'a结束后数据&'a mut T指向的数据是不是没有移动。如果移动了,就违反了Pin约束。

容易犯的一个错误就是忘记隐藏原始变量,因为您可以dropPin并移动&'a mut T背后的数据,如下所示(这违反了Pin约束):

fn main(){letmuttest1=Test::new("test1");letmuttest1_pin=unsafe{Pin::new_unchecked(&muttest1)};Test::init(test1_pin.as_mut());drop(test1_pin);println!(r#"test1.b points to "test1": {:?}..."#,test1.b);letmuttest2=Test::new("test2");mem::swap(&muttest1,&muttest2);println!("... and now it points nowhere: {:?}",test1.b);}

堆上固定

将!Unpin类型固定到堆将为我们的数据提供稳定的地址,所以我们知道指向的数据在固定后将无法移动。与栈固定相反,我们知道数据将在对象的生命周期内固定。

usestd::pin::Pin;usestd::marker::PhantomPinned;​#[derive(Debug)]struct Test{a: String,b: *constString,_marker: PhantomPinned,}​implTest{fn new(txt: &str)-> Pin>{lett=Test{a: String::from(txt),b: std::ptr::null(),_marker: PhantomPinned,};letmutboxed=Box::pin(t);letself_ptr: *constString=&boxed.as_ref().a;unsafe{boxed.as_mut().get_unchecked_mut().b=self_ptr};​boxed}​fn a(self: Pin)-> &'astr{&self.get_ref().a}​fn b(self: Pin)-> &'aString{unsafe{&*(self.b)}}}​pubfn main(){letmuttest1=Test::new("test1");letmuttest2=Test::new("test2");​println!("a: {}, b: {}",test1.as_ref().a(),test1.as_ref().b());println!("a: {}, b: {}",test2.as_ref().a(),test2.as_ref().b());}

有的函数要求与之配合使用的futures是Unpin。对于没有Unpin的Future或Stream,您首先必须使用Box::pin(用于创建Pin>)或pin_utils::pin_mut!宏(用于创建Pin)来固定该值。 Pin>和Pin都可以作为futures使用,并且都实现了Unpin。

例如:

usepin_utils::pin_mut;// `pin_utils` is a handy crate available on crates.io​// A function which takes a `Future` that implements `Unpin`.fn execute_unpin_future(x: implFuture+Unpin){/* ... */}​letfut=async{/* ... */};execute_unpin_future(fut);// Error: `fut` does not implement `Unpin` trait​// Pinning with `Box`:letfut=async{/* ... */};letfut=Box::pin(fut);execute_unpin_future(fut);// OK​// Pinning with `pin_mut!`:letfut=async{/* ... */};pin_mut!(fut);execute_unpin_future(fut);// OK

总结如果是T:Unpin(这是默认设置),则Pin 完全等于&'a mut T。换句话说:Unpin表示即使固定了此类型也可以移动,因此Pin将对这种类型没有影响。

如果是T:!Unpin,获得已固定T的&mut T需要unsafe。

大多数标准库类型都实现了Unpin。对于您在Rust中遇到的大多数“常规”类型也是如此。由async / await生成的Future是此规则的例外。

您可以在nightly使用功能标记添加!Unpin绑定到一个类型上,或者通过在stable将std::marker::PhantomPinned添加到您的类型上。

您可以将数据固定到栈或堆上。

将!Unpin对象固定到栈上需要unsafe。

将!Unpin对象固定到堆并不需要unsafe。使用Box::pin可以执行此操作。

对于T:!Unpin的固定数据,您必须保持其不可变,即从固定到调用drop为止,其内存都不会失效或重新利用。这是pin约束的重要组成部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值