譯自Rust futures: an uneducated, short and hopefully not boring tutorial - Part 2時間:2018-12-03,譯者:
motecshine, 簡介:motecshine
歡迎向Rust中文社區投稿,投稿地址 ,好文將在以下地方直接展示
- Rust中文社區首頁
- Rust中文社區Rust文章欄目
- 知乎專欄Rust語言
- sf.gg專欄Rust語言
- 微博Rustlang-cn
Intro
在這個系列的第一篇文章我們瞭解瞭如何使用Rust Future
.但是隻有我們徹底的瞭解Future
並且操作得當才能發揮它真正的作用。這個系列的第二篇文章,我們將介紹如何避免Future
裏常見的陷阱。
Error troubles
我們將Future
組織成一個鏈
很簡單,只要通過Rust Future
提供的and_then
函數就可以了。但是在上一篇文章中我們使用了Box<Error> trait
作爲錯誤類型,繞過了編譯器的檢查。爲什麼我們沒有使用更爲詳細的錯誤類型?原因很簡單, 每個Future
函數的錯誤返回都有可能不同.
原則1: 當我們將不同的Future
組織成一個調用鏈
時,每個Future
都應該返回相同的Error Type
.
讓我們一起來證明一下這一點.
我們有兩個被叫做ErrorA
和ErrorB
的Error
類型, 我們將會實現error::Error trait
,儘管這並不是編譯器必須讓我們做的(但是這是一個好習慣[在我看來這應該算是一個最佳實踐]),在我們實現error::Error trait
的同時還需要實現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
}
}
// Error B
#[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
}
}
我儘量用簡單的方式去實現Error Trait
,這樣可以排除別的干擾來證明我的觀點. 現在讓我們在Future
中使用ErrorA
與ErrorB
.
fn fut_error_a() -> impl Future<Item = (), Error = ErrorA> {
err(ErrorA {})
}
fn fut_error_b() -> impl Future<Item = (), Error = ErrorB> {
err(ErrorB {})
}
現在讓我們在main
函數裏調用它.
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);
跟我們所預見的結果一致:
fut_error_a == ErrorA
fut_error_b == ErrorB
到現在爲止還挺好的,讓我們把ErrorA
與ErrorB
打包成一個調用鏈:
let future = fut_error_a().and_then(|_| fut_error_b());
我們先調用fut_error_a
然後再調用fut_error_b
,我們不用關心fut_error_a
的返回值所以我們用_
省略不用. 用更復雜的術語解釋就是: 我們將impl Future<Item=(), Error=ErrorA>
和 impl Future<Item=(), Error=ErrorB>
打包成調用鏈.
現在讓我們嘗試編譯這段代碼:
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`
這個報錯非常明顯, 編譯器期待我們使用ErrorB
但是我們給了一個ErrorA
。
原則2: 當我們組織Future Chain
時,第一個錯誤類型必須與最後一個future
返回的錯誤類型一致.(When chaining futures, the first function error type must be the same as the chained one.)
rustc
已經非常明確的告訴我們了. 這個Future chain
最終返回的是ErrorB
所以我們第一個函數也應該返回ErrorB
. 在上述代碼我們返回了ErrorA
, 所以導致編譯失敗.
我們改如何處理這個問題?非常幸運的是, 我們可以使用Rust Future
給我們提供的map_err
方法. 在我們的示例中,我們想要把ErrorA
轉換成ErrorB
,所以我們只需要在ErrorA
與ErrorB
之間調用這個函數就行了.
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);
如果我們現在編譯並運行示例,將會輸出:
mapping ErrorA into ErrorB
error chain == ErrorB
讓我們進一步推動這個例子.假設我們想連接ErrorA,然後是ErrorB,然後再連接ErrorA。 就像是:
let future = fut_error_a()
.and_then(|_| fut_error_b())
.and_then(|_| fut_error_a());
我們最初的解決方式只適合成對的future
, 並沒有考慮其他的情況。所以在上面代碼中我們不得不這麼做:ErrorA => ErrorB => ErrorA
.就像這樣:
let future = fut_error_a()
.map_err(|_| ErrorB::default())
.and_then(|_| fut_error_b())
.map_err(|_| ErrorA::default())
.and_then(|_| fut_error_a());
看上去不那麼優雅但是還是解決了多個Future
的錯誤處理.
"From" to the rescue
簡化上述代碼的一種簡單的方式就是利用std::covert::From
. 當我們實現From
, 這樣編譯器就可以自動的將一個結構軟換爲另一個結構.現在讓我們實現From<ErrorA> for ErrorB
和From<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()
}
}
通過上述的實現我們只需要用from_err
函數來代替map_err
就好了。
let future = fut_error_a()
.from_err()
.and_then(|_| fut_error_b())
.from_err()
.and_then(|_| fut_error_a());
現在的代碼仍然與錯誤轉換混合, 但轉換代碼不再是內聯的,而且代碼可讀性也提高了。Futrue Crate
非常聰明:只有在錯誤的情況下才會調用from_err
代碼, 因此在不使用from_err
時, 也不會在Runtime
時產生額外的開銷.
Lifetimes
Rust簽名功能是引用的顯式生命週期註釋. 但是,大多數情況下,Rust允許我們避免使用生命週期省略來指定生命週期.讓我們看看它的實際效果. 我們想編寫一個帶字符串引用的函數,如果成功則返回相同的字符串引用:
fn my_fn_ref<'a>(s: &'a str) -> Result<&'a str, Box<Error>> {
Ok(s)
}
注意代碼中 <'a>
的部分, 意思是我們顯示的聲明一個生命週期
. 接着我們聲明瞭一個引用形參s: &'a str
, 這個參數必須在'a
生命週期有效的情況下使用.使用Result <&'str,Box <Error >>
,我們告訴Rust
我們的返回值將包含一個字符串引用.只要'a有效,該字符串引用必須有效.換句話說,傳遞的字符串引用和返回的對象必須具有相同的生命週期.這會導致我們的語法非常冗長,以至於Rust允許我們避免在常見情況下指定生命週期。 所以我們可以這樣重寫函數:
fn my_fn_ref(s: &str) -> Result<&str, Box<Error>> {
Ok(s)
}
但是在Future
中你不能這樣寫, 讓我們來嘗試用Future
方式複寫這個函數:
fn my_fut_ref_implicit(s: &str) -> impl Future<Item = &str, Error = Box<Error>> {
ok(s)
}
編譯將會失敗(rustc 1.23.0-nightly (2be4cc040 2017-11-01
):
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.
當然也有解決方式,我們只要顯示聲明一個有效的生命週期就行了:
fn my_fut_ref<'a>(s: &'a str) -> impl Future<Item = &'a str, Error = Box<Error>> {
ok(s)
}
impl Future with lifetimes
在Future
中如果有引用傳參我們必須要顯示的註釋生命週期. 舉個例子, 我們希望使用&s
的值並且返回的是一個沒有引用的String
.我們必須顯示的註釋生命週期:
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)))
}
上面的代碼將會報錯:
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>> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
爲了解決這個錯誤我們必須爲impl Future
追加一個'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)))
}
現在你可以運行這段代碼了:
let retval = reactor
.run(my_fut_ref_chained("str with lifetime"))
.unwrap();
println!("my_fut_ref_chained == {}", retval);
Closing remarks
在下一篇文章中,我們將介紹Reactor
。 我們還將從頭開始編寫未來的實現結構。