原文链接:Pin, Unpin, and why Rust needs them

运用 Rust 异步库通常是很简单的。除了要在各种当地写点 async.await ,就像写一般的 Rust 代码相同approveAPP但当你数据结构有哪些编写自己的异步库的时分就会变得很困难https协议。当安全教育渠道登录进口我第一次尝试的时分,我对 T: ?UnpinPin<&mut Self> 感到十分困惑和难明。我之前从未见过这些类型,我不能了解它们是用来做什么的。不过现在我了解它们了,我写了一个说明器,我期望我到时分能够回来阅安全教育渠道登录读一下。在这篇文章中,咱们将学习:

  • 什么是 Future
  • 什么是自引证类型 self-referential t数据结构与算法ypes ?
  • 为什么它们是不安全的?
  • Pin / Unpin 是怎样让他们安全的?
  • 怎样运用 Pin / Unpin 去编写凌乱嵌套的 Future

什么是 Future

几年前,我需求编写一些需求异步执行的代码,并搜集一些政策。例如,它花了多长的时刻。我想要写一个 TimedWrapper ,它会像下面这样调用:

// Some async function, e.g. polling a URL with [https://docs.rs/reqwest]
// Remember, Rust functionsappstore do nothing until you .await them, so this isn't
// actually making a HTTP request yet.
let async_fn = reqwest::get("http://adamcha数据结构严蔚敏lmers.appstorecom");
​
// Wrap the async function in my hypothetical wrapper.
let timed_async_fn = TimedWrapper::new(async_fn);
​
//链表回转 Call the async function, which will send a HTTP request and time it.
let (resp, time) = timed_async_fn.await;安全中心
println!("G数据结构严蔚敏ot a HTTP {} in {}ms", r数据结构题库esp.unwrap().status(), t链表数据结构ime.as_millis())

我喜欢这个接口,它很简略,团队其他人应该也很简单去运用。让咱们结束它吧!咱们都知道在 Rust 底层 ,异步函数仅仅一个回来 Future 的惯例函数。Future 这个 trait 十分简数据结构略,它意https和http的差异味着这个类型有以下特征:

  • 能够被轮询
  • 当它被轮询的时分,它应该会回来 Pending (待定)或许 Rhttps和http的差异eady (已准备)
  • 假定是 Pending , 应该过稍后再去轮询它
  • 假定是 Ready,它将会链表结构带着一个响应安全教育渠道登录进口值。链表结构咱们就能够称它为 Resolving (处理)安全手抄报

下面是一个简略的结束 Futuappreciatere 的比方,让它回来一个随机的 u16 类型。

use std::{future::Future, pin::Pin, task::Context}
​
/// A future which ret数据结构知识点总结u数据结构严蔚敏第二版课后答案rns a random number w安全教育渠道登录hen it resolves.
#[derive(Default)]
struct RandFuture;
​
impl Future for RandFutur链表数据结构e {
 // Every future has to specify what type of value it returns when it resapproacholv链表c言语es.
 // This particular future will returnappstore a u16.
 type Output = u16;
​
 // The `Future` trait has onl安全教育渠道登录y one method, named "poll".
 fn poll(self: Pin<&mut Self>, _cx: &mut C链表和数组的差异ontext) -> Poll<Self::Output>https认证 {
   Poll::ready(数据结构严蔚敏第二版课后答案rand::random())
  }
}

如同并不难!我想咱们能够准备结束 TimedWrapper 了。

初尝嵌套 Future 并在运用中栽跟头

让咱们先定义一个类型。

pub struct Time安全期是哪几天dWrapper<Fut: Future> {
 start:https协议 Option<Instant>,
 future: Fut,
}

OK,所以 Tim数据结构知识点总结edWrapper 是一个具有 Fut: Future 的泛型类型。它将会存储一个 Futureapple苹果官网 作为一个字段,还有一个 start 字段用于记载它第一次被轮询的时刻。让咱们写一个结构函数。

impl<Fut安全出产法: Future&gapproacht;数据结构严蔚敏pdf TimedWrapper<Fut> {
 pub fn new(future:安全 Fut) -&安全gt; Self {
   Self { future, start: None }
  }
}

这儿没有太凌乱的当地。new 办法链表排序接受一个 Future ,并把它包装在 TimedWrapapproachper 中。当然,咱们有必要设安全中心startNone ,由于数据结构c言语版它还没有被https域名轮询。所以让咱们结束数据结构知识点总结 poll (轮询)函数吧。咱们要做的便是结束 Fu数据结构与算法剖析turehttps安全问题 这个 trait,这样才让他能够运用 .awa安全教育渠道登录进口it

impl<Fut: Future> Futur链表的逆置e for TimedWapple苹果官网rapper<Fut> {
 // This future will output a pair of values:
 // 1. The val链表的特色ue from thappearancee inner future
 //appreciate 2. How long it took for the inn安全er fapp下载uture to resolve
 type Output = (Fut::Output, Duration);
​
 fnhttps安全问题 poll(self: Pi链表的特色n<&mut SelfHTTPS>, cx: &mut Context) -> Poll<Self:链表和数组的差异:Output> {
   // Call the inner poll, meas链表的逆置uring how long it took.
   let start = self.start.get_or_i安全手抄报nsert_with(Instant::apple苹果官网now);
   let inner_poll = self.future.poll(cx);
   let elapsed = s安全手抄报elf.elAPPapsed();
​
   match inner_poll {
     // The inner future needs more time数据结构知识点总结, so this future needs more time too
     Poll::Pending => Poll::Pending,
     // Success!
     Poll::Ready(output) => Poll::Readhttps安全问题y((output, elapsed)),
    }
  }
}

OK,这儿没有太难,可是有一个问题:它不能通过编译。

为什么 Rust 需求 Pin, Unpin ?

所以,Rust 编译器报告了一个过错在 self.future.poll(cx)appreciate 上,奉告咱们在当前效果域中 Fut 上并没有 poll 这个办法。这让人疑问,由于咱们知https协议道,FutFuture 类型,所以他必定有 poll 办法?可是编译器下apple苹果官网面还有一句提示: Pin<&mut Fut> 里边有 poll 办法。所以这个古怪的类型是什么?

好的,咱们链表是线性结构吗都知道办法里边有一个接纳链表结构者 ”re数据结构严蔚敏第二版课后答案ceiver”,它能够操作 self ,接纳者能够是 self, &aappearancemp;self, &mut self ,他们分别代表apple苹果官网了获取 self 的一切权,self 的借用和 self 的可变借用。所以 Pin<&mut self> 是一个全新的,陌生的接纳者。Rus安全中心t 编译器说明晰由于咱们的 Fut ,所以咱们需求 Pin<&mut self> 类型。在这儿面有两个问题:

  1. 什么是 Pin
  2. 假定咱们有一个类型 T, 要怎样获取到 Pin<&mut T>

剩下的文章就来答复这两个问题,我将说明 Rust 中或许导致不安全代码的一些问https协议题,以及为什https认证Pin 能够安全地处理这些问题。

自引证是不安全的

Pin 的存在是为了处理一些特定的问题:自引证数据类型,像appearance据结构中有指向他们自己的指针。举个比方,一个二叉查找树链表是线性结构吗或许会自引证https安全问题的指针,他们指向其他具有相同结构的结点数据结构c言语版

自引证类型十分有用,但他们同时也很难保证内存安全。要了解原因,让咱们用以下类型作为比方:appearance两个字段,一个 i32 字段叫 val,和一个指向 i32 的指针叫 pointer。

为什么 Rust 需求 Pin, Unpin ?

到目前为止,一切正常。pointer 字段指向 val 字段的内存地址A,包含了一个合法安全手抄报的 i32类型数据。一切的指针都是合法的,他们指向的内存都编码了正确的值(在这个比方中是APP i32 类型)。可是 Rust 编译器经常在内存中移动值(一切权转移)。例如,咱们传入这个结构体进入到另一个办法中,它或许被移动到不同的内存地址中,或许咱们应该用 Box 包装它,把它放在堆上。或许,假定这个结构体是在一个 Vec 中,然后咱们插入了链表是线性结构吗一些值进去,这个 Vec 或许会添加他的容量,这时就需求把它一切的元素移入一个安全新的,更大的缓冲区当中。

为什么 Rust 需求 Pin, Unpin ?

当咱们移动它的时分,结构体中的字段就会改动他们的内存地址,链表c言语而不是他们的值。所以 pointer 指针依然指向之前的地址A,但地址A现在没有一个合法的 i32 值,之前地址A的数据现链表和数组的差异已被移动到地址B链表是线性结构吗了,该地址或许被其他的值写入了!所以,现在 pointer 是不合法指针。这很糟糕,最好的情况下不合法指针会构成程序溃散,最坏的情况下,他是一个能够被链表怎样调理长度黑客利用的缝隙。咱们只想要容许这些内存不安全的操作在 unsafeappstore码块里,咱们还要小心谨慎地为该类型写上注释链表排序来奉告运用者们当发生移动的时分要及时更新指安全出产法针。

Unpin 和 !Unpin

为了回忆一下,一切的 Rust 类型能够归为两类:

  1. 在内存中移动是安全的。这数据结构与算法是默许和标准情况下的,例如,包含像数字、字符串、 布尔值等原语,也包含完全由它们组安全成的结构体或枚举类型。大多数类型归于这一类!
  2. 在在内存中移动是不安全的,自引证类型。这是很稀有的,一个比方是 intrusive linked list inappleside some Tokio internals (一些 Tokio 内部结构中的侵入式链表),另一个比方是大多数结束 Future 并借链表结构用数据的类型,原因在 Rust async book 中说明过。

在分类1中的类型在内存中的移动是完全安全的。你在移动它们的时分不会构成任何指针无效。但假定你移动了分类2中的类型,你就会让这些指针安全出产法无效,还或许获得未定义的行为。正如之前咱们看到的,前期的 Rust 版别中你有必要十分细心地运用这些类型,不去移动它们,或许你移动了它们之后运用 unsaf数据结构c言语版e 更新一切的指针数据结构。但在 Rust 1.33 之后,编译器能够主动识别出一https域名切类型是归于哪一类,并保证它们只能被安全地运用。

在分类1中的任何类型都结束了一个安全Unpin 的特别的 auto tra链表回转it。十分古怪的姓名,但他的含义马上就会变得清楚。相同,大多数的APP一般类型结束了 Unpin ,由于他是一个 auto trait (就像 SendSyncSized数据结构与算法 相同),所以你不必忧虑需求自己去结束它。假定你不确定一个类型是否能够被安全的数据结构与算法移动,只需求在 docs.rs 检查它是否结束了 Unpin

分类2中的类型,有个很有构思的姓名 !Unpin ( 里边的! 意味着没有结束)。为了安全地运用这些类型,咱们不能够运用惯例的自引证指针。取而代之的是,我安全中心们运用特别的指针将他们 ”固定“ 在某个当地,以保证它们不会被移动,这便是 Pi链表的逆置n 这个类型的效果。

为什么 Rust 需求 Pin, Unpin ?

Pin 包装了一个指针并阻挠了它的移动,仅有的破例便是假定这个值结束了 Unpin ,咱们就知道了它能够被安全地移动。瞧!现在咱们能够安全地写自引证类型结构体了!这是很重要的,由于在咱们上面安全教育渠道登录进口的谈论中,许多 Future 是自引证类型,而咱们需求它们HTTPS来结束 aysnc/.awhttps协议ait

运用 Pin

现在咱们了解了为什么有 Pin 的存在,和为什么安全教育渠道登录进口咱们的 Future 轮询函数(poll)的接纳者是 Pin<&mut self>HTTPS不是惯例的 &mut self 。所以让咱们回到咱们刚链表结构才遇到的问题:内部的 Future 需求一个在内存中被固定的引证。

更一般地说:给定的一个被固定的结构体,咱们怎样操作他的字段?

处理办法便是编写 helper 函数,为你供给对字段的引证。这些引证将会是像 &approvemu安全教育渠道登录t self 相同的一般引证,或许它们也或许被固定,你能够选择你需求的任意一种。这称为 投影(projection):假定您有一个固定结构体,您能够编写一个投影办法,让你能够访问其一切字段。

投影实际上仅仅将数据传入和传出 Pin。例如,咱们从 Pin<&mut self> 中获取 staapp下载rt: Option<Durati链表排序on>链表的逆置 字段,咱们需求将 Future: Fut 放入 Pin 以便咱们能够调用它的 poll 办法。假定你看过 Pin 的文档 你会知道假定指针指向的是 Unpin 的值,那么它将总是安全的,不然需求用 unsafe

// Putting data into Pin
pub    fn new     <P: Deref<Target:Unpin>>(pointer: P) -> Pin<P>;
pub unsa安全出产法fe f链表结构n new_unchecked<P>           (pointer: P) -> Pin<P>;
​
// Getting data from Pin
pub    fn into_inner     <P: Deref<Target: Unpin>>(pin:安全教育 Pin<P>) -> P;
pub unsafe fn in数据结构知识点总结to_inner_unchecked<P>            (pin: Pin<P>) -> P;

我知道不安全听起来有点吓人,但写 unsafe 代码仍是能让人接受的。我认为 unsafe 是编译器在说 “嘿,我不知道这段代码是否契合这儿的规矩,所以我要依托你来检查我。” Rust 编译器为数据结构题库咱们做了许多作业,咱们时不时地做一些作业才是公平的。 假定你想学习怎样编写链表是线性结构吗自己的投影办法,我强烈推荐这篇关于该主题的文章 fasterthanli.me/articles/pi…。 但咱们现在要链表数据结构走一条捷径 : )

改用 pin-project

好吧,是时分率直了,我不喜欢 unsafe 代码,我知道我刚说明晰为什么能够用 unsafe ,但假定有得选的话,谁会用 unsafe 呢?i( ̄_, ̄ )

我初步写 Rust 并不是由于我想细心考虑代码运转发安全出产法生的成果,哈哈,我仅仅想快安全出产法点且不去损坏东西。 走运的是,有人怜惜我并写了一个能够生成完全安全投影的crate ! 它被称为 pin-project,它很棒。 咱们需求做的便是在定义时加上一些宏:

#[pin_pHTTPSroject::pin_project] // This generates a `project` method
pub struct TimedWrapper<Fut: Future> {
 // For each field, we nee数据结构严蔚敏第二版课后答案d to choose whe链表数据结构th安全手抄报er `project` returns a安全期是哪几天n
 // unpinned (&a数据结构mp;mut T) or pinned (Pin<&mut T>) reference to the field.
 // By default, it assumes数据结构严蔚敏pdf unpinned:
 start: Option<Instant>,
 // Opt into pinned references with th安全期是哪几天is attribute:
 #HTTPS[pin]
 future: Fut,
}

关于每个字段,您有必要选择是否应固定其投影。 默许情况下,应该运用一般引证,由于它们更简略。 可是假定你知道你需求一个固定的引证。例如,由于你想调用 poll ,它的接纳者是 Pin<&安全mut Self> ,那么你链表怎样调理长度能够用 #[pin] 来结束。

现在,咱们终链表结构于能够轮询内部的 Future 了!

fn poll(self: Pin<&mut Se链表回转lf>, cx: &mut Context) -> Poll<Self::Output> {
 // This returns a type with all the same fields, with all the same typ链表es,
 // except that the fields defined with #[pin] will be pinned.
 le数据结构与算法剖析t mut this = sel链表f.project();
​
 // Call the inner poll, measuring how long it took.
 let start = this.start.get_or_insert_with(Instant::now);
 let inner_poll = this.future.as_mut().appearancepol数据结构c言语版l(cx);
 let elapsed = start.elapsed();
​
 match inner_poll {
   // The inner future needs more time, so this future needs more time too
   Poll::Pending => Poll::Pending,
   // Success!
   Poll::Ready(output) => Poll::Ready((output, e链表结构lapsed)),
  }
}

最终,咱们的政策结束了application —— 咱们写完了一切的代码,而且没有运用任何的 unsafe

总结

假定 Rust 类型具有自引证指针,则无法安全地移动它。 毕竟,移动它们并不会更新数据结构严蔚敏pdf指针,所以它们依然指向旧的内存地址,所以它们现在是不合法的。 Ruhttps安全问题st 能够主动判别哪些类型能够安全移动(而且会主动为它们结束 Unpin )。 假定你有一个指向某些数据的固定指针,Rust 能够保证不会发生任何不安全的事情(假定移动是安全的,你能够移动它,假定移动不安全,那么就会报数据结构严蔚敏错)。 这很appear重要,由于许多 Future 类型都是appreciate自引证的,所以我安全教育渠道们需求 Pin 来安全地轮询 Future。 你或许不必自己轮询(只需运用 async/await),但假定您这样做,请安全期计算器运用 pin-project c数据结构严蔚敏pdfrate 来简化你的代码。

原文链接:blog.ada安全教育渠道登录mchalmers.com/pin-unpin/