對(duì)我來(lái)說(shuō),其中之一就是在Rust中
Pin/Unpin
。每次我讀到有關(guān)固定的解釋?zhuān)业拇竽X就像 ,幾周后就像 。
所以,我寫(xiě)這篇文章是為了強(qiáng)迫我的大腦記住這些知識(shí)。我們看看效果如何!
Pin
Pin 是一種指針,可以看作是&mut T
和&T
之間的折中。Pin<&mut T>
的重點(diǎn)是說(shuō):
-
這個(gè)值可以被修改(就像
&mut T
一樣),但是 -
這個(gè)值不能被移動(dòng)(不像
&mut T
)
一個(gè)典型的例子就是自指數(shù)據(jù)結(jié)構(gòu)。在使用
async
時(shí),它們會(huì)自然地出現(xiàn),因?yàn)槲磥?lái)值往往會(huì)在引用自己的本地值。這個(gè)看似溫和的 Future:
async fn self_ref() { let mut v = [1, 2, 3]; let x = &mut v[0]; tokio::from_secs(1)).await; *x = 42; }
需要一個(gè)自我引用的結(jié)構(gòu),因?yàn)樵诘讓樱?code style="background:rgb(251,241,199);font-family:'Source Code Pro', 'Fira Code', Menlo, Monaco, Consolas, 'DejaVu Sans Mono', Inconsolata, 'Courier New', monospace;">futures是狀態(tài)機(jī)(不像閉包)。
請(qǐng)注意,
self_ref
在第一個(gè)await
處將控制權(quán)傳遞回調(diào)用者。這意味著盡管v
和x
看起來(lái)像普通的堆棧變量,但在這里可能發(fā)生了更復(fù)雜的事情。編譯器希望生成類(lèi)似這樣的內(nèi)容:
enum SelfRefFutureState { Unresumed, // Created and wasn't polled yet. Returned, Poisoned, // `panic!`ed. SuspensionPoint1, // First `await` point. } struct SelfRefFuture { state: SelfRefFutureState, v: [i32; 3], x: &'problem mut i32, // a "reference" to an element of `self.v`, // which is a big problem if we want to move `self`. // (and we didn't even consider borrowchecking!) }
但是!如果你想的話,你可以移動(dòng)
SelfRefFuture
,這會(huì)導(dǎo)致x
指向無(wú)效的內(nèi)存。
let f = self_ref(); let boxed_f = Box::new(f); // Evil? let mut f1 = self_ref(); let mut f2 = self_ref(); std::swap(&mut f1, &mut f2); // Blasphemy?
怎么回事?就像一位聰明的編譯器曾經(jīng)說(shuō)過(guò)的:
futures do nothing unless you這是因?yàn)檎{(diào)用.await
orpoll
them#[warn(unused_must_use)]
on by default – rustc
self_ref
實(shí)際上什么都不做, 我們實(shí)際上會(huì)得到類(lèi)似于:
struct SelfRefFuture { state: SelfRefFutureState, v: MaybeUninit<[i32; 3]>, x: *mut i32, // a pointer into `self.v`, // still a problem if we want to move `self`, but only after it is set. // // .. other locals, like the future returned from `tokio::sleep`. }
那么在這種狀態(tài)(初始狀態(tài))下可以安全地移動(dòng)。
impl SelfRefFuture { fn new() -> Self { Self { state: SelfRefFutureState::Unresumed, v: MaybeUninit::uninit(), x: std::null_mut(), // .. } } }
只有當(dāng)我們開(kāi)始在
f
上進(jìn)行輪詢(xún)時(shí),我們才會(huì)遇到自我引用的問(wèn)題(x
指針被設(shè)置),但如果 f 被包裹在Pin
中,所有這些移動(dòng)都變成了unsafe
,這正是我們想要的。 由于許多futures 一旦執(zhí)行就不應(yīng)該在內(nèi)存中移動(dòng),只有將它們包裝在Pin
中才能安全地使用,因此與異步相關(guān)的函數(shù)往往接受Pin<&mut T>
(假設(shè)它們不需要移動(dòng)該值)。
一個(gè)微小的例子
這里不需要固定:use tokio::timeout; async fn with_timeout_once() { let f = async { 1u32 }; let _ = timeout(Duration::from_secs(1), f).await; }
但是如果我們想要多次調(diào)用 timeout (例如,因?yàn)槲覀兿胍卦嚕?,我們將不得不使?code style="background:rgb(251,241,199);font-family:'Source Code Pro', 'Fira Code', Menlo, Monaco, Consolas, 'DejaVu Sans Mono', Inconsolata, 'Courier New', monospace;">&mut f(否則會(huì)得到
use of moved value
),這將導(dǎo)致編譯器報(bào)錯(cuò)
use tokio::timeout; async fn with_timeout_twice() { let f = async { 1u32 }; // error[E0277]: .. cannot be unpinned, consider using `Box::pin`. // required for `&mut impl Future ` to implement `Future`let _ = timeout(Duration::from_secs(1), &mut f).await; // An additional retry. let _ = timeout(Duration::from_secs(1), &mut f).await; }
為什么? 因?yàn)樵趲讉€(gè)層級(jí)下,
timeout
調(diào)用了被定義為Future::poll
的函數(shù)
fn poll(self: Pin<&mut Self>, ...) -> ... { ... }
當(dāng)我們
await
f
時(shí),我們放棄了對(duì)它的所有權(quán)。 編譯器能夠?yàn)槲覀兲幚砉潭ㄒ?,但如果我們只提供一個(gè)&mut f
,它就無(wú)法做到這一點(diǎn),因?yàn)槲覀兒苋菀灼茐?Pin 的不變性:
use tokio::timeout; async fn with_timeout_twice_with_move() { let f = async { 1u32 }; // error[E0277]: .. cannot be unpinned, consider using `Box::pin`. let _ = timeout(Duration::from_secs(1), &mut f).await; // .. because otherwise, we could move `f` to a new memory location, after it was polled! let f = *Box::new(f); let _ = timeout(Duration::from_secs(1), &mut f).await; }
這個(gè)時(shí)候我們需要給 future 套上一個(gè)
pin!
use tokio::pin; use tokio::timeout; async fn with_timeout_twice() { let f = async { 1u32 }; pin!(f); // f is now a `Pin<&mut impl Future >`.let _ = timeout(Duration::from_secs(1), &mut f).await; let _ = timeout(Duration::from_secs(1), &mut f).await; }
這里還需要再做一點(diǎn)額外的工作,我們需要確保
f
在被 pin 包裹之后不再可訪問(wèn)。如果我們看不到它,就無(wú)法移動(dòng)它。 事實(shí)上我們可以更準(zhǔn)確地表達(dá)不能移動(dòng)規(guī)則:指向的值在值被丟棄之前不能移動(dòng)(無(wú)論何時(shí)丟棄Pin
)。這就是
pin!
宏的作用:它確保原始的f
對(duì)我們的代碼不再可見(jiàn),從而強(qiáng)制執(zhí)行Pin
的不變性 Tokio’spin!
是這樣實(shí)現(xiàn)的:
// Move the value to ensure that it is owned let mut f = f; // Shadow the original binding so that it can't be directly accessed // ever again. #[allow(unused_mut)] let mut f = unsafe { Pin::new_unchecked(&mut f) };
標(biāo)準(zhǔn)庫(kù)的版本
pin!
有點(diǎn)更酷,但使用的是相同的原理:用新創(chuàng)建的Pin
來(lái)遮蔽原始值,使其無(wú)法再被訪問(wèn)和移動(dòng)。
一個(gè)
所以Pin
是一個(gè)指針(對(duì)另一個(gè)指針的零大小的包裝器),它有點(diǎn)像&mut T
但有更多的規(guī)則。 下一個(gè)問(wèn)題將是“歸還借用的數(shù)據(jù)”。 我們無(wú)法回到以前的固定未來(lái)
use std::Future; async fn with_timeout_and_return() -> impl Future { let f = async { 1u32 }; pin!(f); // f is now a `Pin<&mut impl Future>`.let s = async move { let _ = timeout(Duration::from_secs(1), &mut f).await; }; // error[E0515]: cannot return value referencing local variable `f` s }
現(xiàn)在應(yīng)該更清楚為什么了:被固定的
f
現(xiàn)在是一個(gè)指針,它指向的數(shù)據(jù)(異步閉包)在我們從函數(shù)返回后將不再存在。 因此,我們可以使用Box::pin
-pin!(f); +let mut f = Box::pin(f);
但是我們剛剛不是說(shuō)
Pin<&mut T>
是&mut T
和&T
之間的(一個(gè)包裝器)指針嗎? 嗯,一個(gè)mut Box
也像一個(gè)&mut T
,但有所有權(quán)。 所以一個(gè)Pin>
是一個(gè)指向可變Box
和不可變Box
之間的指針,值可以被修改但不能被移動(dòng)。
Unpin
Unpin
是一種 Trait。它不是Pin
的"相反",因?yàn)?code style="background:rgb(251,241,199);font-family:'Source Code Pro', 'Fira Code', Menlo, Monaco, Consolas, 'DejaVu Sans Mono', Inconsolata, 'Courier New', monospace;">Pin是指針的一種類(lèi)型,而特征不能成為指針的相反。Unpin
也是一個(gè)自動(dòng)特性(編譯器在可能的情況下會(huì)自動(dòng)實(shí)現(xiàn)它),它標(biāo)記了一種類(lèi)型,其值在被固定后可以被移動(dòng)(例如,它不會(huì)自我引用)。主要的觀點(diǎn)是,如果
T: Unpin
,我們總是可以Pin::new
和Pin::{into_inner,get_mut}
T 的值,這意味著我們可以輕松地在“常規(guī)”的可變值之間進(jìn)行轉(zhuǎn)換,并忽略直接處理固定值所帶來(lái)的復(fù)雜性。Unpin
Trait 是Pin
的一個(gè)重要限制,也是Box::pin
如此有用的原因之一:當(dāng)T: !Unpin
時(shí),“無(wú)法移動(dòng)或替換Pin>
的內(nèi)部”,因此Box::pin
(或者更準(zhǔn)確地說(shuō)是Box::into_pin
)可以安全地調(diào)用不安全的Pin::new_unchecked
,而得到的Box
總是Unpin
的,因?yàn)橐苿?dòng)它時(shí)并不會(huì)移動(dòng)實(shí)際的值。這里說(shuō)的很繞,我們用例子例子解釋一下。
另一個(gè)微小的例子
我們可以親手創(chuàng)造一個(gè)美好的 Future:fn not_self_ref() -> impl Future u32> + Unpin { struct Trivial {} impl Future for Trivial { type Output = u32; fn poll(self: Pin<&mut Self>, _cx: &mut std::Context<'_>) -> std::Poll { std::Ready(1) } } Trivial {} }
現(xiàn)在,我們可以多次調(diào)用它而不需要固定: timeout
async fn not_self_ref_with_timeout() { let mut f = not_self_ref(); let _ = timeout(Duration::from_secs(1), &mut f).await; let _ = timeout(Duration::from_secs(1), &mut f).await; }
使用
async fn
或async {}
語(yǔ)法創(chuàng)建的任何 Future 都被視為!Unpin
,這意味著一旦我們將其放入Pin
中,就無(wú)法再取出來(lái)。
摘要
-
Pin
是對(duì)另一個(gè)指針的包裝,有點(diǎn)像&mut T
,但額外的規(guī)則是在值被丟棄之前,移動(dòng)它所指向的值是不安全的。 -
為了安全地處理自引用結(jié)構(gòu),我們必須在設(shè)置自引用字段后防止其移動(dòng)(使用
Pin
)。 -
Pin 承諾該值在其生命周期內(nèi)無(wú)法移動(dòng),所以我們無(wú)法在不放棄創(chuàng)建
&mut T
的能力并破壞Pin
的不變性的情況下創(chuàng)建它。 -
當(dāng)在擁有所有權(quán)的 Future 進(jìn)行
await
Future 時(shí),編譯器可以處理固定,因?yàn)樗酪坏┧袡?quán)轉(zhuǎn)移,Future
就不會(huì)移動(dòng)。 -
否則,我們需要處理固定(例如使用
pin!
或Box::pin
) -
Unpin
是一個(gè)標(biāo)記特征,表示一個(gè)類(lèi)型即使在被包裝在Pin
之后仍然可以安全地移動(dòng),使一切變得更簡(jiǎn)單。 -
大多數(shù)結(jié)構(gòu)是
Unpin
,但async fn
和async {}
總是產(chǎn)生!Unpin
結(jié)構(gòu)。
-
指針
+關(guān)注
關(guān)注
1文章
484瀏覽量
71185 -
數(shù)據(jù)結(jié)構(gòu)
+關(guān)注
關(guān)注
3文章
573瀏覽量
40743 -
編輯器
+關(guān)注
關(guān)注
1文章
822瀏覽量
32023 -
PIN
+關(guān)注
關(guān)注
1文章
311瀏覽量
25448 -
Rust
+關(guān)注
關(guān)注
1文章
234瀏覽量
7094
原文標(biāo)題:摘要
文章出處:【微信號(hào):Rust語(yǔ)言中文社區(qū),微信公眾號(hào):Rust語(yǔ)言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
詳解Rust的泛型
如何在Rust中讀寫(xiě)文件
Rust的多線程編程概念和使用方法
怎樣去使用Rust進(jìn)行嵌入式編程呢
RUST在嵌入式開(kāi)發(fā)中的應(yīng)用是什么
在Rust代碼中加載靜態(tài)庫(kù)時(shí),出現(xiàn)錯(cuò)誤 ` rust-lld: error: undefined symbol: malloc `怎么解決?
Linux內(nèi)核中整合對(duì) Rust 的支持
Rust在虛幻引擎5中的使用
重點(diǎn)講解Send與Sync相關(guān)的并發(fā)知識(shí)
Rust中的錯(cuò)誤處理方法
rust語(yǔ)言基礎(chǔ)學(xué)習(xí): rust中的錯(cuò)誤處理
Rust的內(nèi)部工作原理

評(píng)論