学习
实践
活动
专区
工具
TVP
写文章

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语言学习交流

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:automanyang
原始发表时间:2020-05-06
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 详解Pytorch里的pin_memory 和 non_blocking

    pin_memory 和 non_blocking的作用分别是什么?网上看了很多解释,只是稀里糊涂的有个感觉,就是用了这玩意速度能变快,但是不知所以然,这篇文章...

    marsggbo
  • Rust中的workspace

    java项目中用maven管理代码时,如果遇到大型工程,一般会拆分成不同的模块,比如spring-mvc中,通常会按model, view, controlle...

    菩提树下的杨过
  • Rust 欧洲之声|真实世界中的 Rust

    我最喜欢的一篇关于编程语言的论文之一是讲「在商业层面,没有人会为实现一些功能而去改变编程语言」,这是一个残酷的现实,但是这种需求是存在的。我和商业投资人都看到的...

    张汉东
  • Rust中的模式匹配

    在其它一些语言中,let x = 5 之类的语句,仅仅只是赋值语句。但是在rust中,可以换个角度理解,认为5这个值匹配到了x变量。如果觉得有些牵强,可以再来一...

    菩提树下的杨过
  • Rust中的过程宏

    Rust 吉祥物是只螃蟹,Ferris,这可以理解,但是它为什么被煮了啊?都变红了。

    杨永贞
  • rust 中的结构体

    在许多语言中,我们都早就接触过结构体这种复合数据类型,在面向对象的语言中,类的概念与之非常类似,在 rust 语言中,结构体同样是一种实用且强大的数据类型,那么...

    用户3147702
  • Rust中的Result枚举

    Result枚举在Rust中是使用频率极高的一个类型,常用于函数的返回值定义,其源码如下:

    菩提树下的杨过
  • Rust 中的错误处理 - Rust 实践指南

    对于程序员来说,错误处理的重要性是不言而喻的,贯穿于代码编写、开发、调试,以及交付运行的全过程中。对于此等重要的工作,Rust 生态中特别有一个 crate e...

    niqin.com
  • Rust中的泛型

    泛型程序设计是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。泛型编程的中...

    端碗吹水
  • 【Rust日报】2021-09-04 cURL 中的 Rust

    Allen Wyma 与 cURL 的原作者 Daniel 谈论在 cURL 中使用 Rust。

    MikeLoveRust
  • Rust中对某个结构体实现方法于rust中的关联函数

    这里的impl里面就为Rectangle结构体实现了相应的面积计算方法,在调用时与其他语言相同,也是直接通过点的方式调用实例结构体上的方法。self会被推断成R...

    gzq大数据
  • Rust 中的 QUIC 实现 --- quinn

    QUIC 是基于 UDP 的多路复用、安全传输协议。可以简单理解为在用户空间将 TCP 里的机制实现了一遍,比如拥塞控制、流量控制等。好处是升级比较方便,TCP...

    谛听
  • 聊聊Rust中的move语义

    用户4700054
  • 【Rust日报】 2019-06-25:Rust中的记忆化

    有一种技术叫记忆化(memoization),可以避免函数的多次计算,从而节省资源。顾名思义,记忆化技术可以把函数的调用结果记忆下来,或者说缓存下来。

    MikeLoveRust
  • Rust中的代码组织:package/crate/mod

    刚接触Rust遇到一堆新概念,特别是package, crate, mod 这些,特别迷糊,记录一下

    菩提树下的杨过
  • 【Rust日报】2022-05-14 Rust 中的 Streams 指引

    此版本最大的变化是 Lapce 将 GPU 后端从 Wgpu 更改为 OpenGL,以实现更好的兼容性,特别是双源混合功能使我们能够进行子像素文本渲染。除此之外...

    MikeLoveRust
  • 【Rust日报】2020-12-28 Rust中的闭包

    这是一篇详细讲解 rust 中闭包的文章. 不仅从闭包的使用,更是从闭包的原理来更深入的理解闭包.

    MikeLoveRust
  • 【Rust日报】2022-12-28 Rust 中的原生反射

    反射是程序检查自身结构和行为的能力。例如,在 Javascript 中,可以编写迭代任意对象的键值对的程序,或者检查对象是否包含给定名称的字段。Deflect ...

    MikeLoveRust
  • 【Rust每周一知】Rust中的读写锁RwLock

    在计算机科学中,有一些经典的同步问题,读者-作家问题就是其中一个,该问题涉及多个并发线程试图同时访问同一共享资源的情况。

    MikeLoveRust

扫码关注腾讯云开发者

领取腾讯云代金券