Rust中的Pin详解

相关概念

Pin<P<T>>

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

Pin<P>定义如下:

pub struct Pin<P> {
    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<P>的实现

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

impl<P: Deref<Target: Unpin>> Pin<P> {
    pub fn new(pointer: P) -> Pin<P> {
        unsafe { Pin::new_unchecked(pointer) }
    }
}

可以看出,只有P所指向的T: Unpin,才可以new出一个Pin<P<T>>。这里的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<T>> {
    (box x).into()
}

例如,自定义了Node结构,如下的代码生成pin实例:

let node_pined: Pin<Box<Node>> = 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<T>&mut T)的结构,在T: Unpin的情况下,Pin<Box<T>>Box<T>是类似的,通过DerefMut就可以直接得到&mut T,在T没有实现Unpin的情况下,Pin<Box<T>>只能通过Deref得到&T,就是说T被pin住了。

Pin这种自废武功的方法怪怪的,为什么要有Pin?虽然Box、Rc、Arc等指针类型也可以让实例在heap中固定,但是这些指针的safe方法会暴露出&mut T,这就会导致T的实例被移动,比如通过std::mem::swap方法,也可以是Option::take方法,还可能是Vec::set_lenVec::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 { ... }` block
struct AsyncFuture {
    ...
    fut_one: FutOne,
    fut_two: FutTwo,
    state: State,
}

// List of states our `async` block can be in
enum 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<Self>或者&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<Self::Item, Self::Error>;
}

总结一下

  • Pin实际是对P指针的限制,在T没有实现Unpin的情况下,避免P指针暴露&mut Self
  • Pin的引入是Async/.Await异步编程的需要,核心就是Future::poll方法参数的需要。
  • 除了Future::poll方法之外,不建议使用Pin,也没有必要使用Pin.

本文分享自微信公众号 - Rust语言学习交流(rust-china),作者:automanyang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【Rust日报】2021-01-08 Rust Search Extension 1.1.0发布

    Rust Search Extension发布了最新版,同时也突破了500个star,感谢大家的支持!这个版本主要功能如下:

    MikeLoveRust
  • 【译文】Rust异步编程: Pinning

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

    袁承兴
  • 【Rust日报】2021-10-29 Rust 培养提高计划

    腾讯会议地址:https://meeting.tencent.com/dm/mlhs6YsYuFRb

    MikeLoveRust
  • 【翻译】200行代码讲透RUST FUTURES (6)

    让我们直接了当的说吧,Pin是这一系列概念中很难一开始就搞明白的,但是一旦你理解了其心智模型,就会觉得非常容易理解.

    MikeLoveRust
  • 【Rust日报】2019-09-24 Rust小程序为何会卡顿?

    Graphlib是一个为图数据结构提供通用且易于使用的API的rust图形库,它的API与std::collections中的其他数据结构相似,它是为了在Pur...

    MikeLoveRust
  • 零成本异步 I/O (下)

    这个非常出色的基于轮询的新方案——我们编写了这个模型,我归功于 Alex 和 Aaron Turon,是他们提出了这个想法——不是由 Future 来调度回调函...

    MikeLoveRust
  • 【翻译】200行代码讲透RUST FUTURES (5)

    生成器的动机可以在 RFC#2033中找到。它写得非常好,我建议您通读它(它谈论async/await的内容和谈论生成器的内容一样多)。

    MikeLoveRust
  • Rust高并发编程总结

    Serverless的概念火了,业界已经不再讨论要不要用Serverless的问题了,而是高喊Serverless First的口号力求快速拥抱Serverle...

    beyondma
  • TiKV Rust Client 迁移记 - Futures 0.1 至 0.3

    最近我将一个中小型的 crate 从 futures 库的 0.1 迁移至了 0.3 版本。过程本身不是特别麻烦,但还是有些地方或是微妙棘手,或是没有很好的文档...

    PingCAP
  • 【Rust日报】2020-05-24 Rash, Rocket, Mun, Casbin

    Rash是一种受Ansible工具启发的Declarative Shell脚本语言。

    MikeLoveRust
  • 【Rust 日报】2021-8-26 Pin,Unpin为什么Rust需要它们

    又是一篇讲Pin的blog,是作者本人在学习Rust异步过程中做的一些总结和理解,方便大家在学习异步时遇到相关疑惑可以查阅。

    MikeLoveRust
  • libcopp接入C++20 Coroutine和一些过渡期的设计

    最近GCC 10.1.0 发布,三大编译器(MSVC、GCC、Clang)都已经支持了C++20协程,之前给 libcopp 接入 C++20协程 的计划也就提...

    owent
  • 【Rust日报】2019-09-02 - Rocket和Actix-Web的异步性能测试

    Rust目前最火的两个web框架就是Actix-Web和Rocket, 众所周知,Rocket的优势在于易用性,Actix-web在于性能,最近,Rocket的...

    MikeLoveRust
  • Rust异步浅谈

      这篇文章主要描述了Rust中异步的原理,Rust异步也是在最近的版本中(1.39)中才稳定下来。希望可以通过这边文章在提高自己认知的情况下,也可以给读者带来...

    MikeLoveRust
  • Rust 1.39.0 发布,async/.await 稳定了

    Rust 1.39.0 已经发布。此版本的亮点包括 async/.await,对 match 守卫 by-move 绑定的共享引用,以及函数参数的属性。

    Debian中国
  • 【Rust日报】2020-12-04 Glommio:一个基于Linux io_uring的高效多核线程调度库

    Glommio是一个基于Linux io_uring的高效多核线程调度库,它可以让你的程序做到thread-per-core级别的线程调度,每个线程只分配给一个...

    MikeLoveRust
  • 【Rust日报】 2019-07-13:Rust哪邊不安全?

    作者提到 unsafe 關鍵字讓很多人誤解了 rust 在 unsafe裡面很不安全,

    MikeLoveRust
  • 【翻译】RUST无锁编程

    本文内容译自Lock-freedom without garbage collection,中间有少量自己的修改.

    MikeLoveRust
  • 官方 RustConf 2021 盘点

    在很久之前,为标准库添加同步原语。其中最重要的是 Mutex。因为操作系统已经为我们实现了很多同步原语,那么标准库只需要将它们包起来即可吗?答案是否定的。因为这...

    张汉东

扫码关注云+社区

领取腾讯云代金券