怎样理解Rust中的Pin

怎样理解Rust中的Pin,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

员工经过长期磨合与沉淀,具备了协作精神,得以通过团队的力量开发出优质的产品。创新互联建站坚持“专注、创新、易用”的产品理念,因为“专注所以专业、创新互联网站所以易用所以简单”。公司专注于为企业提供成都网站建设、网站设计、微信公众号开发、电商网站开发,小程序设计,软件按需设计等一站式互联网企业服务。

相关概念

Pin>
这是一个struct,作用就是将P所指向的T在内存中固定住,不能移动。说白一些,就是不能通过safe代码拿到&mut T
Pin

 定义如下:

  pub struct Pin

 {    pointer: P,}

Unpin
这是一个trait,定义在  std::marker  中,如果一个  T: Unpin  ,就说明T在pin后可以安全的移动,实际就是可以拿到&mut T
  
  
  pub auto trait Unpin {}
!Unpin
对Unpin取反,!Unpin的双重否定就是pin。如果一个类型中包含了PhantomPinned,那么这个类型就是!Unpin。

pub struct PhantomPinned;
#[stable(feature = "pin", since = "1.33.0")]impl !Unpin for PhantomPinned {}
 

Pin

的实现

我们这里只关注safe方法,重点是new方法:
  impl> Pin

 {    pub fn new(pointer: P) -> Pin

 {        unsafe { Pin::new_unchecked(pointer) }    }}

可以看出,只有P所指向的  T: Unpin  ,才可以new出一个  Pin>  。这里的T就是应该被pin的实例,可是由于  T: Unpin  实际上T的实例并不会被pin。也就是说,T没有实现Unpin trait时,T才会被真正的pin住。
由于  Pin::new  方法要求  T: Unpin  ,通常创建一个不支持Unpin的T的pin实例的方法是用  Box::pin  方法,定义如下:
  pub fn pin(x: T) -> Pin> {    (box x).into()}
例如,自定义了Node结构,如下的代码生成pin实例:
  let node_pined: Pin> = Box::pin(Node::new());let movded_node_pined = node_pined;
Node没有实现Unpin时,通过Pin的安全方法都不能得到  &mut Node  ,所以就不能移动Node实例。注意,这里是不能移动Node实例,node_pined是Pin实例,是可以移动的。
当然,通过Pin的unsafe方法,仍然可以得到  mut Node  ,也可以移动Node实例,但这些unsafe的操作就需要程序员自己去承担风险。Pin相关方法中对此有很详细的说明。
Pin可以被看作一个限制指针(Box&mut T)的结构,在T: Unpin的情况下,Pin>Box是类似的,通过DerefMut就可以直接得到&mut T,在T没有实现Unpin的情况下,Pin>只能通过Deref得到&T,就是说T被pin住了。
Pin这种自废武功的方法怪怪的,为什么要有Pin?虽然Box、Rc、Arc等指针类型也可以让实例在heap中固定,但是这些指针的safe方法会暴露出&mut T,这就会导致T的实例被移动,比如通过  std::mem::swap  方法,也可以是  Option::take  方法,还可能是  Vec::set_len  、  Vec::resize  方法等,这些可都是safe等方法。这些方法的共同点都是需要  &mut Self  ,所以说只要不暴露  &mut Self  ,就可以达到pin的目标。

为什么需要pin?

事情的起因就是Async/.Await异步编程的需要。
看看如下异步编程的代码:
  let fut_one = /* ... */;let fut_two = /* ... */;async move {    ...    fut_one.await;    ...    fut_two.await;    ...}
rustc在编译是会自动生成类似如下的代码,其中的AsyncFuture会是一个自引用结构:
 
// The `Future` type generated by our `async { ... }` blockstruct AsyncFuture {    ...    fut_one: FutOne,    fut_two: FutTwo,    state: State,}
// List of states our `async` block can be inenum State {    AwaitingFutOne,    AwaitingFutTwo,    Done,}
impl Future for AsyncFuture {    type Output = ();
   fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {        ...    }}
 
注意  Future::poll  方法的第一个参数是  Pin<&mut Self>  ,如果在  Future::poll  方法中有类似  std::mem::swap  等方法调用,就有可能导致AsyncFuture被移动,那么AsyncFuture中的自引用field就会导致灾难。
可能你也注意到了,这里的  Future::poll  代码是自动生成的,可以不调用  std::mem::swap  等方法,就不会导致AsyncFuture被移动。的确是这样的,如果在这里将  Future::poll  的第一个参数改为  Box  或者  &mut Self  ,大概率是没有问题的。很多executor的实现,都是要求Future是支持Unpin,因为在poll代码中的确有修改Self的需求,但不会产生错误,也是这个原因。
但是,对于程序员实现Future的情况,问题就来了。**如果poll的参数是&mut Self,那么程序员就可能使用safe代码(比如std::mem::swap)产生错误,这是与rust安全编码的理念相冲突的。**这就是Pin引入的根本原因!
其实,在future 0.1版本中,poll的这个参数就是  &mut Self  ,如下:
  
  
  pub trait Future {    type Item;    type Error;    fn poll(&mut self) -> Poll;}
  • Pin实际是对P指针的限制,在T没有实现Unpin的情况下,避免P指针暴露 &mut Self
  • Pin的引入是Async/.Await异步编程的需要,核心就是 Future::poll 方法参数的需要。
  • 除了 Future::poll 方法之外,不建议使用Pin,也没有必要使用Pin.

关于怎样理解Rust中的Pin问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。


网页名称:怎样理解Rust中的Pin
标题网址:http://scjbc.cn/article/jcopjg.html

其他资讯