本文翻譯自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)闯袒。
我們有ErrorA
和ErrorB
兩個(gè)類(lèi)型虎敦,我們均為其實(shí)現(xiàn)error::Error
游岳,盡管編譯器并沒(méi)有這樣的強(qiáng)制要求,但是我自認(rèn)為這是個(gè)好習(xí)慣其徙。
Error
trait同時(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 ErrorB
和FromInto<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)盖溺。