原文標題:Understanding Futures in Rust -- Part 2
原文連接:https://www.viget.com/articles/understanding-futures-is-rust-part-2/
公衆號: Rust 碎碎念
翻譯 by: Prayinghtml
若是你尚未看前面的內容,能夠在這裏[1]查看(譯註:已有譯文,可在公衆號查看)。web
在第一部分,咱們介紹了 Future trait,瞭解了 future 是如何被建立和運行的,而且開始知道它們如何能被連接到一塊兒。
api
上次內容的代碼能夠在這個 playground 連接[2]查看,而且本文中全部示例代碼將會以這段代碼爲基礎。
數組
注意:全部的代碼示例都有對應的 playground 連接,其中一些用於解釋說明但沒法編譯的代碼會有相應的標記。promise
若是你熟悉 JavaScript 中的 promise 而且閱讀了最新的博客,你可能會對先前文章中提到的組合子(then
、catch
和finally
)感到困惑。
安全
你將會在本文章找到與它們對等的東西,而且在最後,下面這段代碼將可以編譯。你將會理解使得 future 可以運做的類型,trait 和底層概念。服務器
// This does not compile, yet
fn main() {
let my_future = future::ready(1)
.map(|x| x + 3)
.map(Ok)
.map_err(|e: ()| format!("Error: {:?}", e))
.and_then(|x| future::ready(Ok(x - 3)))
.then(|res| {
future::ready(match res {
Ok(val) => Ok(val + 3),
err => err,
})
});
let val = block_on(my_future);
assert_eq!(val, Ok(4));
}
首先,咱們須要一些工具函數,future::ready
和block_on
。這些函數可以讓咱們很容易地建立和運行 future 直到它們完成,這些函數雖然有用,可是在生產環境的代碼中並不常見。
網絡
在開始以前,咱們先把咱們的Future
trait 和Context
結構體整合到模塊裏以避免和標準庫衝突。閉包
mod task {
use crate::NOTIFY;
pub struct Context<'a> {
waker: &'a Waker,
}
impl<'a> Context<'a> {
pub fn from_waker(waker: &'a Waker) -> Self {
Context { waker }
}
pub fn waker(&self) -> &'a Waker {
&self.waker
}
}
pub struct Waker;
impl Waker {
pub fn wake(&self) {
NOTIFY.with(|f| *f.borrow_mut() = true)
}
}
}
use crate::task::*;
mod future {
use crate::task::*;
pub enum Poll<T> {
Ready(T),
Pending,
}
pub trait Future {
type Output;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output>;
}
}
use crate::future::*;
Playground 連接[3]app
這裏惟一須要注意的就是,只有將模塊,類型和函數公開,才能在代碼中使用它們。這能夠經過pub
關鍵字來完成。
future::ready
建立了一個 future,該 future 帶有傳入值而且是當即就緒(ready)的。當你有一個已經不是 future 的值的時候,這個函數能夠用於開啓一個 future 鏈,就像前一個示例那樣。
mod future {
// ...
pub struct Ready<T>(Option<T>);
impl<T> Future for Ready<T> {
type Output = T;
fn poll(&mut self, _: &Context) -> Poll<Self::Output> {
Poll::Ready(self.0.take().unwrap())
}
}
pub fn ready<T>(val: T) -> Ready<T> {
Ready(Some(val))
}
}
fn main() {
let my_future = future::ready(1);
println!("Output: {}", run(my_future));
}
Playground 連接[4]
咱們建立了一個類型爲Ready<T>
的泛型結構體,該結構體包裝了一個Option
。這裏咱們使用Option
枚舉以保證 poll 函數只被調用一次。在 executor 的實現中,在返回一個Poll::Ready
以後調用 poll 將會報錯。
爲了咱們的目標,咱們把咱們的 run 函數重命名爲block_on
。在future-preview
這個 crate 中,該函數使用內部的LocalPool
來運行一個 future 直到完成,同時會阻塞當前線程。咱們的函數也作了類似的事情。
fn block_on<F>(mut f: F) -> F::Output
where
F: Future,
{
NOTIFY.with(|n| loop {
if *n.borrow() {
*n.borrow_mut() = false;
let ctx = Context::from_waker(&Waker);
if let Poll::Ready(val) = f.poll(&ctx) {
return val;
}
}
})
}
fn main() {
let my_future = future::ready(1);
println!("Output: {}", block_on(my_future));
}
Playground 連接[5]
首先,讓咱們從一些可以讓你直接做用於另外一個 Future 的Output
值的一些組合子開始。在本文中,咱們使用非正式的可是比較流行的組合子定義,即可以容許你對某種類型執行操做,並與其餘類型結合起來的函數。例如,一個嵌套的 future 能夠由一個組合子函數函數建立,它能夠有一個複雜的類型Future< Output = Future < Output = i32>>
。這能夠被稱爲一個 future,該 future 的輸出(Output)是另外一個 future,新的 future 的輸出是 i32 類型。這樣的組合子中,最簡單的一個就是map
。
若是你熟悉Result
或者Option
類型的map
函數,那麼對它應該不陌生。map 組合子持有一個函數並將其應用到 future 的Output
值上,返回一個新的 future,這個新 future 把函數的結果(Result)做爲它的Output
。Future 中的 map 組合子甚至比Result
或者Option
中更簡單,由於不須要考慮 failure 的狀況。map 就是簡單的Future->Future
。
下面是函數簽名:
// does not compile
fn map<U, F>(self: Sized, f: F) -> Map<Self, F>
where
F: FnOnce(Self::Output) -> U,
Self: Sized,
map
是一個泛型函數,它接收一個閉包,返回一個實現了 Future 的Map
結構體。不是每當咱們在值上進行連接都須要實現Future
trait,正如咱們在最後一部分作的那樣,咱們可使用這些函數來爲咱們完成這些工做。
讓咱們來分析一下:
Map<Self, F>
聲明瞭 map 函數的(返回)類型,包括當前的 future,以及傳入函數的 future。
where
是一個可以讓咱們添加類型約束的關鍵字。對於F
類型參數,咱們能夠在內部定義約束map<U, F: FnOnce(Self::Output) -> U
,可是使用 where 語句可讀性會更好。
FnOnce(Self::Output) -> U
是一個函數的類型定義,該函數接收當前類型的Output
並返回任意類型U
。FnOnce
是函數 trait 中的一個,其餘還包括FnMut
和Fn
。FnOnce
是用起來最簡單的,由於編譯器能夠保證這個函數只被調用一次。它使用環境中用到的值並獲取其全部權。Fn
和FnMut
分別以不可變和可變的方式借用環境中值的引用。全部的閉包都實現了FnOnce
trait,而且其中一些沒有移動值的閉包還實現了FnMut
和Fn
trait。這是 Rust 作的最酷的事情之一,容許對閉包和第一類函數參數進行真正有表達力的使用。Rust book 中的相關內容[6]值得一讀。
Self: Sized
是一個約束,容許map
只能被Sized
的 trait 實現者調用。你沒必要考慮這個問題,可是確實有些類型不是Sized
。例如,[i32]
是一個不肯定大小的數組。由於咱們不知道它多長。若是咱們想要爲它實現咱們的Future
trait,那麼咱們就不能對它調用map
。
大多數組合子都遵循這個模式,所以接下來的文章咱們就不須要分析的這麼仔細了。
下面是一個map
的完整實現,它的Map
類型以及它對Future
的實現
mod future {
trait Future {
// ...
fn map<U, F>(self, f: F) -> Map<Self, F>
where
F: FnOnce(Self::Output) -> U,
Self: Sized,
{
Map {
future: self,
f: Some(f),
}
}
}
// ...
pub struct Map<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, F, T> Future for Map<Fut, F>
where
Fut: Future,
F: FnOnce(Fut::Output) -> T,
{
type Output = T;
fn poll(&mut self, cx: &Context) -> Poll<T> {
match self.future.poll(cx) {
Poll::Ready(val) => {
let f = self.f.take().unwrap();
Poll::Ready(f(val))
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1).map(|val| val + 1);
println!("Output: {}", block_on(my_future));
}
Playground 連接[7]
從高層次來說,當咱們調用一個 future 上的map
時,咱們構造了一個Map
類型,該類型持有當前 future 的引用以及咱們傳入的閉包。Map
對象自身也是一個 Future。當它被輪詢時,它依次輪詢底層的 future。當底層的 future 就緒後,它獲取那個 future 的Output
的值而且把它傳入閉包,對Poll::Ready
中的閉包返回的值進行包裝(wrapping)而且把新值向上傳遞。
若是你閱讀了最新的博客,你對在這裏看到的東西應該感到很熟悉,可是在咱們繼續以前,我會快速地講解做爲一個複習。
pub struct Map<Fut, F>
是一個關於 future——Fut
和函數F
的泛型。
f: Option<F>
是一個包裝了閉包了Option
類型。這裏是個小技巧,以保證閉包只被調用一次。當你獲取一個Option
的值,它會用None
替換內部的值而且返回裏面包含的值。若是在返回一個Poll::Ready
以後被輪詢,這個函數會 panic。在實際中,future 的 executor 不會容許這種狀況發生。
type Output = T;
定義了 map future 的輸出和咱們的閉包的返回值是將會是相同的。
Poll::Read(f(val))
返回帶有閉包返回結果的就緒(ready)狀態。
Poll::Pending => Poll::Pending
若是底層的 future 返回 pending,繼續傳遞。
future::ready(1).map(|val| val + 1);
這對就緒(ready)future 的輸出進行了 map,並對其加 1。它返回了一個 map future,其中帶有對原先的 future 的一個引用。map future 在運行期間輪詢原先的 future 是否就緒(ready)。這和咱們的AddOneFuture
作的是相同的事情。
這真的很酷,主要有如下幾個緣由。首先,你沒必要對每個你想要進行的計算都實現一個新的 future,它們能夠被包裝(wrap)進組合子。事實上,除非你正在實現你本身的異步操做,不然你可能歷來都不須要本身去實現Future
trait。
如今咱們有了map
,咱們能夠把任何咱們想要的計算連接起來,對麼?答案是對的,可是對此還有一個至關大的警告。
想象一下,當你有一些函數,這些函數返回你想要連接起來的 future。對於這個例子,咱們能夠想象,它們是下面的 api 調用,這些調用返回包裝(wrap)在 future 中的結果,get_user
和get_files_for_user
。
// does not compile
fn main() {
let files_future = get_user(1).map(|user| get_files_for_user(user));
println!("User Files: {}", block_on(files_future));
}
這段代碼沒法編譯,可是你能夠想象你在這裏構建的類型,看起來應該像這樣:Future<Output = Future<Output= FileList>>
。這在使用Result
和Option
類型的時候也是一個常見問題。使用map
函數常常會致使嵌套的輸出和對這些嵌套的繁瑣處理。在這種狀況下,你不得不去跟蹤到底嵌套了多少層而且對每個嵌套的 future 都調用block_on
。
幸運地是,Result
,Option
有一個被稱爲and_then
的解決方案。Option
的and_then
經過對T
應用一個函數來映射(map)Some(T) -> Some(U)
,而且返回閉包所返回的Option
。對於 future,它是經過一個稱爲then
的函數來實現的,該函數看起來很像映射(map),可是這個閉包應該它本身的 future。在一些語言中,這被稱爲flatmap
。這裏值得注意的是,傳遞給then
的閉包返回的值必須是實現了Future
,不然你將會獲得一個編譯器錯誤。
這裏是咱們的對於then
,Then
結構體和它的對Future
trait 的實現。其中的大部份內容和咱們在 map 中作的很像。
mod future {
trait Future {
// ...
fn then<Fut, F>(self, f: F) -> Then<Self, F>
where
F: FnOnce(Self::Output) -> Fut,
Fut: Future,
Self: Sized,
{
Then {
future: self,
f: Some(f),
}
}
}
// ...
pub struct Then<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, NextFut, F> Future for Then<Fut, F>
where
Fut: Future,
NextFut: Future,
F: FnOnce(Fut::Output) -> NextFut,
{
type Output = NextFut::Output;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
match self.future.poll(cx) {
Poll::Ready(val) => {
let f = self.f.take().unwrap();
f(val).poll(cx)
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1)
.map(|val| val + 1)
.then(|val| future::ready(val + 1));
println!("Output: {}", block_on(my_future));
}
Playground 連接[8]
這裏面沒見過的代碼多是f(val).poll(cx)
。它調用了帶有先前 future 的閉包而且直接返回給你poll
的值。
聰明的你可能會意識到,咱們的Then::poll
函數可能會 panic。若是第一個 future 返回就緒(ready)可是第二個 future 返回Poll::Pending
,接着let f = self.f.take().unwrap();
這行代碼就會在下次被輪詢(poll)的時候 panic 並退出程序。在future-preview
中,這種狀況會經過一個稱爲Chain[9]的類型來處理。Chain 經過 unsafe 代碼塊來實現,而且使用了新類型——Pin
。這些內容超出了本文的範圍。目前來說,咱們能夠假定任何經過then
閉包返回的 future 都毫不會返回Poll::Pending
。整體來說,這不是個安全的假設。
在 futures-rs 庫的 0.1 版本中,Future
trait 和Result
類型緊密關聯。Future
trait 的定義以下:
// does not compile
trait Future {
type Item;
type Error;
fn poll(self) -> Poll<Self::Item, Self::Error>;
}
Poll
類型裏定義了成功狀態、失敗狀態和未就緒狀態。這意味着像map
這種函數只有當 Poll 是就緒而且不是錯誤的狀況下才能執行。儘管這會產生一些困擾,可是它在連接組合子而且根據成功或失敗狀態作決定的時候,會產生一些很是好的人體工程學(ergonomics )。
這與std::future
的實現方式有所不一樣。如今 future 要麼是就緒或者是未就緒,對於成功或失敗語義是不可知的。它們能夠包含任何值,包括一個Result
。爲了獲得便利的組合子,好比像map_err
可以讓你只改變一個嵌套的 Result 中的錯誤類型,或者想and_then
這樣,容許你只改變嵌套 Result 中的值類型,咱們須要實現一個新的 trait。下面是TryFuture
的定義:
mod future {
//...
pub trait TryFuture {
type Ok;
type Error;
fn try_poll(self, cx: &mut Context) -> Poll<Result<Self::Ok, Self::Error>>;
}
impl<F, T, E> TryFuture for F
where
F: Future<Output = Result<T, E>>,
{
type Ok = T;
type Error = E;
fn try_poll(&mut self, cx: &Context) -> Poll<F::Output> {
self.poll(cx)
}
}
}
Playground 連接[10]
TryFuture
是一個 trait,咱們能夠爲任意的類型<F, T, E>
實現這個 trait,其中F
實現了Future
trait,它的Output
類型是Result<T,E>
。它只有一個實現者。那個實現者定義了一個try_poll
函數,該函數與Future
trait 上的poll
有相同的簽名,它只是簡單地調用了poll
方法。
這意味着任何一個擁有 Result 的Output
類型的 future 也可以訪問它的成功/錯誤(success/error)狀態。這也使得咱們可以定義一些很是方便的組合子來處理這些內部 Result 類型,而沒必要在一個map
或and_then
組合子內顯示地匹配Ok
和Err
類型。下面是一些可以闡述這個概念的實現。
讓咱們回顧以前想象到的 API 函數。假定它們如今處於會發生網絡分區和服務器中斷的現實世界中,不會老是能返回一個值。這些 API 方法實際上會返回一個嵌有 result 的 future 以代表它已經完成,而且是要麼是成功完成,要麼是帶有錯誤的完成。咱們須要去處理這些結果,下面是咱們多是根據現有工具處理它的方式。
// does not compile
fn main() {
let files_future = get_user(1).then(|result| {
match result {
Ok(user) => get_files_for_user(user),
Err(err) => future::ready(Err(err)),
}
});
match block_on(files_future) {
Ok(files) => println!("User Files: {}", files),
Err(err) => println!("There was an error: {}", err),:w
};
}
狀況還不算太壞,可是假定你想要連接更多的 future,事情很快就會變得一團糟。幸運的是,咱們能夠定義一個組合子——and_then
,該組合子將會把類型Future<Output = Result<T, E>>
映射到Future<Output = Result<U, E>>
,其中咱們把T
變爲了U
。
下面是咱們定義它的方式:
mod future {
pub trait TryFuture {
// ...
fn and_then<Fut, F>(self, f: F) -> AndThen<Self, F>
where
F: FnOnce(Self::Ok) -> Fut,
Fut: Future,
Self: Sized,
{
AndThen {
future: self,
f: Some(f),
}
}
}
// ...
pub struct AndThen<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, NextFut, F> Future for AndThen<Fut, F>
where
Fut: TryFuture,
NextFut: TryFuture<Error = Fut::Error>,
F: FnOnce(Fut::Ok) -> NextFut,
{
type Output = Result<NextFut::Ok, Fut::Error>;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
match self.future.try_poll(cx) {
Poll::Ready(Ok(val)) => {
let f = self.f.take().unwrap();
f(val).try_poll(cx)
}
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1)
.map(|val| val + 1)
.then(|val| future::ready(val + 1))
.map(Ok::<i32, ()>)
.and_then(|val| future::ready(Ok(val + 1)));
println!("Output: {:?}", block_on(my_future));
}
Playground 連接[11]
你對此應該較爲熟悉。事實上,這和then
組合子的實現基本一致。只有一些關鍵的區別須要注意:
函數定義在 TryFuture trait 中
type Output = Result<NextFut::Ok, Fut::Error>;
代表 AndThen future 的輸出擁有新的 future 的值類型,以及在它以前的 future 的錯誤類型。換句話說,若是先前的 future 的輸出包含一個錯誤類型,那麼這個閉包將不會被執行。
咱們調用的是try_poll
而不是poll
。
值得注意的是,當你像這樣來連接組合子的時候,它們的類型前面可能會變得很長且在編譯錯誤信息中難以閱讀。and_then
函數要求 future 調用時的錯誤類型和由閉包返回的類型必須是相同的。
回到咱們的想象的 api 調用。假定調用的 api 都返回帶有同一類錯誤的 future,可是你須要在調用之間進行額外的步驟。假定你必須解析第一個 api 結果真後把它傳遞給第二個。
// 沒法編譯
fn main() {
let files_future = get_user(1)
.and_then(|user_string| parse::<User>())
.and_then(|user| get_files_for_user(user));
match block_on(files_future) {
Ok(files) => println!("User Files: {}", files),
Err(err) => println!("There was an error: {}", err),:w
};
}
這看起來很好,可是沒法編譯,而且會有個晦澀的錯誤信息說它指望獲得像ApiError
的東西可是卻找到了一個ParseError
。你能夠在解析返回的Result
上使用過map_err
組合子,可是對於 future 應該如何處理呢?若是咱們爲 TryFuture 實現一個map_err
,那麼咱們能夠重寫成下面這樣:
// 沒法編譯
fn main() {
let files_future = get_user(1)
.map_err(|e| format!("Api Error: {}", e))
.and_then(|user_string| parse::<User>())
.map_err(|e| format!("Parse Error: {}", e))
.and_then(|user| get_files_for_user(user))
.map_err(|e| format!("Api Error: {}", e));
match block_on(files_future) {
Ok(files) => println!("User Files: {}", files),
Err(err) => println!("There was an error: {}", err),:w
};
}
若是這讓你看着比較混亂,請繼續關注本系列的第三部分,我將談談如何處理這個問題和你可能會在使用 future 時遇到的其餘問題。
下面是咱們實現map_err
的方式
mod future {
pub trait TryFuture {
// ...
fn map_err<E, F>(self, f: F) -> MapErr<Self, F>
where
F: FnOnce(Self::Error) -> E,
Self: Sized,
{
MapErr {
future: self,
f: Some(f),
}
}
}
// ...
pub struct MapErr<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, F, E> Future for MapErr<Fut, F>
where
Fut: TryFuture,
F: FnOnce(Fut::Error) -> E,
{
type Output = Result<Fut::Ok, E>;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
match self.future.try_poll(cx) {
Poll::Ready(result) => {
let f = self.f.take().unwrap();
Poll::Ready(result.map_err(f))
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1)
.map(|val| val + 1)
.then(|val| future::ready(val + 1))
.map(Ok)
.and_then(|val| future::ready(Ok(val + 1)))
.map_err(|_: ()| 5);
println!("Output: {:?}", block_on(my_future));
}
Playground 連接[12]
惟一比較陌生的地方是Poll::Ready(result.map_err(f))
。在這段代碼裏,咱們傳遞咱們的閉包到Result
類型的map_err
函數裏。
如今,文章開頭的代碼能夠運行了!比較酷的是這些全都是咱們本身實現的。還有不少其餘用途的組合子,可是它們幾乎都是相同的方式構建的。讀者能夠本身練習一下,試試實現一個map_ok
組合子,行爲相似於TryFuture
上的map_err
可是適用於成功的結果。
Playground 連接[13]
Rust 中的 Future 之因此如此強大,是由於有一套能夠用於連接計算和異步調用的組合子。
咱們也學習了 Rust 強大的函數指針 trait,FnOnce
,FnMut
和Fn
。
咱們已經瞭解瞭如何使用嵌入在 future 中的 Result 類型。
在第三部分中,咱們將會介紹使錯誤處理沒有那麼痛苦的方式,當你有不少分支時,如何處理返回的 future,以及咱們將深刻到 async/await 這個使人激動的世界。
這裏: https://www.viget.com/articles/understanding-futures-in-rust-part-1/
[2]這個playground連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c354bc3ffaf4cbb5502e839f96459023
[3]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6cebb88919bd65411178ce8019a3aa06
[4]Playground 連接: https://www.viget.com/articles/understanding-futures-is-rust-part-2/
[5]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9e7fca1f3c6f2f5f91b25622db71635f
[6]Rust book中的相關內容: https://doc.rust-lang.org/book/ch13-01-closures.html
[7]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9c427527c64b4dd5238c508de1d4151a
[8]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d86c1223ed4318dcbfa3539ca9a021f2
[9]Chain: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/stream/trait.StreamExt.html#method.chain
[10]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=78daa6a5e60df17d8334199c43fe1e36
[11]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=71fe0962974657f6b9be25510a652b3d
[12]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f9a6cc9cddaac1a43a85bc24db436964
[13]Playground 連接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=92a88fffb74ad350a4db1970b646c41f