Rust修行之Future篇-part2

本文翻譯自Rust futures: an uneducated, short and hopefully not boring tutorial - Part 2

介紹

在前一篇文章中叙淌,我們介紹了怎樣在rust中使用Future,如果掌握了正確的方法瓮具,F(xiàn)uture將是很強(qiáng)大并且易于使用的特性薛耻。在這一篇文章中茸炒,我們將主要關(guān)注一些常見(jiàn)的陷阱爷肝,以及如何避免踩坑。

錯(cuò)誤問(wèn)題

在上一篇文章中蔓搞,我們簡(jiǎn)單的通過(guò)and_then函數(shù)將Future組織成了一條鏈瑞筐,為了繞過(guò)編譯器檢查凄鼻,我們使用Box<Error> trait作為錯(cuò)誤類(lèi)型。使用Box包裹錯(cuò)誤一定程度上增加了復(fù)雜度聚假,為什么不使用更加精確的錯(cuò)誤類(lèi)型呢块蚌?原因在于,當(dāng)把不同的Future級(jí)聯(lián)成鏈時(shí)魔策,每一個(gè)Future都必須返回相同的錯(cuò)誤類(lèi)型匈子。

rust級(jí)聯(lián)準(zhǔn)則:

在進(jìn)行Future級(jí)聯(lián)時(shí),所有的Future返回的錯(cuò)誤類(lèi)型必須是相同的

下面我們來(lái)證實(shí)這一點(diǎn)闯袒。

我們有ErrorAErrorB兩個(gè)類(lèi)型虎敦,我們均為其實(shí)現(xiàn)error::Error游岳,盡管編譯器并沒(méi)有這樣的強(qiáng)制要求,但是我自認(rèn)為這是個(gè)好習(xí)慣其徙。

Errortrait同時(shí)要求實(shí)現(xiàn)std::fmt::Display胚迫,所以我們定義如下:

#[derive(Debug, Default)]
pub struct ErrorA {}

impl fmt::Display for ErrorA {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ErrorA!")
    }
}

impl error::Error for ErrorA {
    fn description(&self) -> &str {
        "Description for ErrorA"
    }

    fn cause(&self) -> Option<&error::Error> {
        None
    }
}

ErrorB是類(lèi)似的:

#[derive(Debug, Default)]
pub struct ErrorB {}

impl fmt::Display for ErrorB {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ErrorB!")
    }
}

impl error::Error for ErrorB {
    fn description(&self) -> &str {
        "Description for ErrorB"
    }

    fn cause(&self) -> Option<&error::Error> {
        None
    }
}

為了證實(shí)我們的觀點(diǎn),我盡可能簡(jiǎn)化了實(shí)現(xiàn)方法⊥倌牵現(xiàn)在我們?cè)贔uture中使用這些結(jié)構(gòu)體访锻。

fn fut_error_a() -> impl Future<Item = (), Error = ErrorA> {
    err(ErrorA {})
}

fn fut_error_b() -> impl Future<Item = (), Error = ErrorB> {
    err(ErrorB {})
}

接下來(lái),我們執(zhí)行這些Future

let retval = reactor.run(fut_error_a()).unwrap_err();
println!("fut_error_a == {:?}", retval);

let retval = reactor.run(fut_error_b()).unwrap_err();
println!("fut_error_b == {:?}", retval);

返回結(jié)果與我們的預(yù)期一致

fut_error_a == ErrorA
fut_error_b == ErrorB

到目前為止闹获,并沒(méi)有出現(xiàn)什么驚喜期犬,我們開(kāi)始來(lái)級(jí)聯(lián):

let future = fut_error_a().and_then(|_| fut_error_b());

這個(gè)Future鏈執(zhí)行的過(guò)程很簡(jiǎn)單,先調(diào)用fut_error_a避诽,然后再調(diào)用fut_error_b龟虎。由于我們不關(guān)心fut_error_a的返回值,所以我們用下劃線來(lái)設(shè)置閉包參數(shù)沙庐,表示丟棄fut_error_a的返回值鲤妥。

可以用更復(fù)雜的術(shù)語(yǔ)來(lái)表達(dá)這一行,我們將impl Future<Item=(), Error=ErrorA>impl Future<Item=(), Error=ErrorB>打包成了一個(gè)調(diào)用鏈拱雏。

我們嘗試編譯后棉安,得到了如下的結(jié)果:

Compiling tst_fut2 v0.1.0 (file:///home/MINDFLAVOR/mindflavor/src/rust/tst_future_2)
error[E0271]: type mismatch resolving `<impl futures::Future as futures::IntoFuture>::Error == errors::ErrorA`
   --> src/main.rs:166:32
    |
166 |     let future = fut_error_a().and_then(|_| fut_error_b());
    |                                ^^^^^^^^ expected struct `errors::ErrorB`, found struct `errors::ErrorA`
    |
    = note: expected type `errors::ErrorB`
               found type `errors::ErrorA`

報(bào)錯(cuò)提示是很直白的,編譯器期待一個(gè)ErrorB的類(lèi)型铸抑,但是卻提供了一個(gè)ErrorA.

用簡(jiǎn)單的說(shuō)法就是:

在進(jìn)行Future級(jí)聯(lián)的時(shí)候贡耽,第一個(gè)函數(shù)的錯(cuò)誤類(lèi)型,必須與級(jí)聯(lián)的下一個(gè)函數(shù)相同羡滑。

這個(gè)原則也是編譯器想告訴我們的. 第二個(gè)函數(shù)返回的是ErrorB類(lèi)型菇爪,所以第一個(gè)函數(shù)必須也返回ErrorB類(lèi)型。然而柒昏,實(shí)際上第一個(gè)函數(shù)返回類(lèi)型是ErrorA,所以編譯失敗熙揍。

應(yīng)該怎樣處理這個(gè)問(wèn)題呢职祷,rust提供了一個(gè)map_err方法,利用這個(gè)方法我們可以轉(zhuǎn)換錯(cuò)誤類(lèi)型届囚。在我們的例子中有梆,我們需要把ErrorA轉(zhuǎn)換為ErrorB,轉(zhuǎn)換過(guò)程如下:

let future = fut_error_a()
    .map_err(|e| {
        println!("mapping {:?} into ErrorB", e);
        ErrorB::default()
    })
    .and_then(|_| fut_error_b());

let retval = reactor.run(future).unwrap_err();
println!("error chain == {:?}", retval);

重新編譯運(yùn)行意系,我們得到了相同的結(jié)果:

mapping ErrorA into ErrorB
error chain == ErrorB

我們將這個(gè)例子復(fù)雜化泥耀,假設(shè)我們想連接ErrorA,然后是ErrorB蛔添,然后再連接ErrorA

let future = fut_error_a()
    .and_then(|_| fut_error_b())
    .and_then(|_| fut_error_a());

為了處理錯(cuò)誤類(lèi)型痰催,我們轉(zhuǎn)換錯(cuò)誤類(lèi)型:

let future = fut_error_a()
    .map_err(|_| ErrorB::default())
    .and_then(|_| fut_error_b())
    .map_err(|_| ErrorA::default())
    .and_then(|_| fut_error_a());

盡管這種方法比較繁瑣兜辞,但是能解決問(wèn)題。

通過(guò)實(shí)現(xiàn)From簡(jiǎn)化代碼

簡(jiǎn)化上述代碼的一種簡(jiǎn)單的方式就是利用std::covert::From trait夸溶。當(dāng)我們?yōu)橐粋€(gè)struct實(shí)現(xiàn)了From后逸吵,編譯器可以自動(dòng)的將一個(gè)結(jié)構(gòu)軟換為另一個(gè)結(jié)構(gòu)。現(xiàn)在讓我們實(shí)現(xiàn)From<ErrorA> for ErrorBFromInto<ErrorB> for ErrorA缝裁。

impl From<ErrorB> for ErrorA {
    fn from(e: ErrorB) -> ErrorA {
        ErrorA::default()
    }
}

impl From<ErrorA> for ErrorB {
    fn from(e: ErrorA) -> ErrorB {
        ErrorB::default()
    }
}

實(shí)現(xiàn)了From后扫皱,我們可以使用from_err()取代map_err()函數(shù),而rust編譯器會(huì)自動(dòng)進(jìn)行類(lèi)型的轉(zhuǎn)換捷绑。

let future = fut_error_a()
   .from_err()
   .and_then(|_| fut_error_b())
   .from_err()
   .and_then(|_| fut_error_a());

現(xiàn)在的代碼看起來(lái)簡(jiǎn)潔了一些韩脑,但是仍然混雜了代碼錯(cuò)誤處理, 但至少轉(zhuǎn)換代碼不再是內(nèi)聯(lián)的,代碼可讀性顯著提高粹污。Futrue Crate非常聰明扰才,只有在錯(cuò)誤的情況下才會(huì)調(diào)用from_err代碼, 因此不會(huì)在Runtime時(shí)產(chǎn)生額外的開(kāi)銷(xiāo)厕怜。

生命周期

rust簽名功能是指顯式的標(biāo)注引用的的生命周期衩匣,大多數(shù)情況下,rust允許我們使用缺省的生命周期粥航。我們來(lái)看一個(gè)實(shí)際的例子琅捏,編寫(xiě)一個(gè)帶字符串引用參數(shù)的函數(shù),返回原字符串的引用递雀。

fn my_fn_ref<'a>(s: &'a str) -> Result<&'a str, Box<Error>> {
    Ok(s)
}

注意代碼中的<'a>, 通過(guò)這個(gè)標(biāo)記柄延,我們顯式的聲明了一個(gè)生命周期。然后缀程,我們聲明了一個(gè)引用形參s: &'a str, 這個(gè)參數(shù)必須在'a生命周期有效的情況下使用搜吧。使用Result<&' str, Box<Error>>,我們告訴rust杨凑,我們的返回值將包含一個(gè)字符串引用滤奈,并且只有當(dāng)’a有效時(shí),該字符串引用才是有效的撩满。換句話說(shuō)蜒程,傳遞的字符串引用和返回的對(duì)象必須具有相同的生命周期。這些設(shè)定導(dǎo)致我們的語(yǔ)法非常冗長(zhǎng)伺帘,以至于rust允許我們使用缺省的生命周期昭躺。 所以我們可以這樣重寫(xiě)函數(shù):

fn my_fn_ref(s: &str) -> Result<&str, Box<Error>> {
    Ok(s)
}

在這個(gè)函數(shù)中,沒(méi)有顯示的生命周期標(biāo)記伪嫁,盡管生命周期的概念仍然存在领炫,這樣的寫(xiě)法簡(jiǎn)化了代碼實(shí)現(xiàn)。

在目前的Future中张咳,不能使用缺省生命周期處理引用帝洪, 讓我們來(lái)嘗試用Future的方式重寫(xiě)這個(gè)函數(shù):

fn my_fut_ref_implicit(s: &str) -> impl Future<Item = &str, Error = Box<Error>> {
    ok(s)
}

這樣的寫(xiě)法將會(huì)編譯失斔贫妗:

Compiling tst_fut2 v0.1.0 (file:///home/MINDFLAVOR/mindflavor/src/rust/tst_future_2)
error: internal compiler error: /checkout/src/librustc_typeck/check/mod.rs:633: escaping regions in predicate Obligation(predicate=Binder(ProjectionPredicate(ProjectionTy { substs: Slice([_]), item_def_id: DefId { krate: CrateNum(15), index: DefIndex(0:330) => futures[59aa]::future[0]::Future[0]::Item[0] } }, &str)),depth=0)
  --> src/main.rs:39:36
   |
39 | fn my_fut_ref_implicit(s: &str) -> impl Future<Item = &str, Error = Box<Error>> {
   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

note: rustc 1.23.0-nightly (2be4cc040 2017-11-01) running on x86_64-unknown-linux-gnu

thread 'rustc' panicked at 'Box<Any>', /checkout/src/librustc_errors/lib.rs:450:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.

當(dāng)然也有解決方法,我們只要顯示聲明一個(gè)有效的生命周期就行了:

fn my_fut_ref<'a>(s: &'a str) -> impl Future<Item = &'a str, Error = Box<Error>> {
    ok(s)
}

實(shí)現(xiàn)包含生命周期的Future

在Future中碟狞,如果用引用傳參啄枕,我們必須要顯式的標(biāo)記生命周期. 舉個(gè)例子, 在Future中接受一個(gè)&str參數(shù),返回一個(gè)String族沃,我們必須顯示的標(biāo)記生命周期频祝。

fn my_fut_ref_chained<'a>(s: &'a str) -> impl Future<Item = String, Error = Box<Error>> {
    my_fut_ref(s).and_then(|s| ok(format!("received == {}", s)))
}

由于返回的Future沒(méi)有被限制在'a的生命周期內(nèi),這段代碼將會(huì)報(bào)錯(cuò):

error[E0564]: only named lifetimes are allowed in `impl Trait`, but `` was found in the type `futures::AndThen<impl futures::Future, futures::FutureResult<std::string::String, std::boxed::Box<std::error::Error + 'static>>, [closure@src/main.rs:44:28: 44:64]>`
  --> src/main.rs:43:42
   |
43 | fn my_fut_ref_chained<'a>(s: &'a str) -> impl Future<Item = String, Error = Box<Error>> {
   |                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

為了解決這個(gè)錯(cuò)誤,我們必須為impl Future追加一個(gè)'a生命周期:

fn my_fut_ref_chained<'a>(s: &'a str) -> impl Future<Item = String, Error = Box<Error>> + 'a {
    my_fut_ref(s).and_then(|s| ok(format!("received == {}", s)))
}

執(zhí)行這個(gè)Future:

let retval = reactor
    .run(my_fut_ref_chained("str with lifetime"))
    .unwrap();
println!("my_fut_ref_chained == {}", retval);

我們得到的預(yù)期結(jié)果如下:

my_fut_ref_chained == received == str with lifetime

尾聲

在下一篇文章中脆淹,我們將介紹Reactor常空,同時(shí),我們還將從零開(kāi)始編寫(xiě)一個(gè)Future的實(shí)現(xiàn)盖溺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載漓糙,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末烘嘱,一起剝皮案震驚了整個(gè)濱河市昆禽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蝇庭,老刑警劉巖醉鳖,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異哮内,居然都是意外死亡盗棵,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)北发,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纹因,“玉大人,你說(shuō)我怎么就攤上這事琳拨〔t恰!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵从绘,是天一觀的道長(zhǎng)寄疏。 經(jīng)常有香客問(wèn)我,道長(zhǎng)僵井,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任驳棱,我火速辦了婚禮批什,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘社搅。我一直安慰自己驻债,他們只是感情好乳规,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著合呐,像睡著了一般暮的。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上淌实,一...
    開(kāi)封第一講書(shū)人閱讀 52,184評(píng)論 1 308
  • 那天冻辩,我揣著相機(jī)與錄音,去河邊找鬼拆祈。 笑死恨闪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的放坏。 我是一名探鬼主播咙咽,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淤年!你這毒婦竟也來(lái)了钧敞?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤麸粮,失蹤者是張志新(化名)和其女友劉穎溉苛,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體豹休,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炊昆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了威根。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凤巨。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖洛搀,靈堂內(nèi)的尸體忽然破棺而出敢茁,到底是詐尸還是另有隱情,我是刑警寧澤留美,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布彰檬,位于F島的核電站,受9級(jí)特大地震影響谎砾,放射性物質(zhì)發(fā)生泄漏逢倍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一景图、第九天 我趴在偏房一處隱蔽的房頂上張望较雕。 院中可真熱鬧,春花似錦、人聲如沸亮蒋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)慎玖。三九已至贮尖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趁怔,已是汗流浹背湿硝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痕钢,地道東北人图柏。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像任连,于是被迫代替她去往敵國(guó)和親蚤吹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容