本文翻譯自Rust futures: an uneducated, short and hopefully not boring tutorial-part1
介紹
既然能夠看到這個頁面仑撞,想必各位看官至少應(yīng)該是一個程序員,并且很有可能是中意這門新語言的衷咽。如果各位看官還有愛逛論壇或者社區(qū)的習(xí)慣,或多或少可能看到過關(guān)于Future的討論钾唬。大量優(yōu)秀的項(xiàng)目已經(jīng)改由Future重構(gòu)底層實(shí)現(xiàn)十办,比如Hyper仓犬,并且這種轉(zhuǎn)變正在成為趨勢嗅绰,如果對rust感興趣,F(xiàn)uture是一個不容錯過的特性。盡管受到大量的關(guān)注窘面,但不能否認(rèn)的是翠语,F(xiàn)uture是一個很難理解的概念,如果各位看官對自己的英文水平與rust基礎(chǔ)很自信民镜,可以直接看官方的Crichton's tutorial啡专,但是對于像我這樣的不太自信的選手,我認(rèn)為官方文檔并不適合作為學(xué)習(xí)教材制圈。
我猜想我可能并不是唯一的一個不太自信的選手,所以我把我的一些思考和實(shí)踐記錄下來畔况,希望這個文檔能幫到大家鲸鹦。
概要
Future的本質(zhì)特征就是其字面意思,指一系列不會立即執(zhí)行跷跪,但是可能在未來某個時(shí)間執(zhí)行的函數(shù)馋嗜。標(biāo)準(zhǔn)函數(shù)一旦被調(diào)用將會立即執(zhí)行,為什么要引入Future這樣的概念呢吵瞻?原因很多葛菇,包括性能,架構(gòu)的優(yōu)雅性橡羞,可擴(kuò)展性等眯停。Future的缺點(diǎn)也十分明顯,即很難用代碼實(shí)現(xiàn)卿泽。通常在一個線程內(nèi)莺债,函數(shù)是串行執(zhí)行的,但是當(dāng)不確定一個函數(shù)會在什么時(shí)候執(zhí)行時(shí)签夭,就需要精巧的設(shè)計(jì)這些調(diào)用之間的因果關(guān)系齐邦,否則,程序的執(zhí)行流程就會亂套了第租。
由于執(zhí)行時(shí)間的不確定帶來了極大的編程難度措拇,rust語言本身增加了大量的輔助機(jī)制來幫助并不精通此道的程序員們使用這個特性。
Rust Future
Rust的Future總是用Result來表達(dá)慎宾,也就是說丐吓,需要顯式指定返回值類型以及錯誤類型。
我們先定義一個普通函數(shù)璧诵,然后把它轉(zhuǎn)換為Future汰蜘。簡單示例如下所示,這個示例返回一個u32或者是被Box包裹的Error trait:
fn my_fn() -> Result<u32, Box<Error>> {
Ok(100)
}
這個函數(shù)轉(zhuǎn)換為Future如下所示:
fn my_fut() -> impl Future<Item = u32, Error = Box<Error>> {
ok(100)
}
這兩段代碼有兩處不同:
1 返回值不再是Result族操,而是一個impl Future,這樣的標(biāo)記允許我們返回一個Future。
2 返回參數(shù)列表為<Item = u32, Error = Box<Error>>泼舱,精確描述了返回值與錯誤類型。
兩個函數(shù)的返回值是不一樣的娇昙,在普通函數(shù)中,用Ok(100)返回笤妙,這種寫法使用到了Ok枚舉類型,而在Future函數(shù)中蹲盘,使用的是小寫的ok方法。
函數(shù)改造完成后召衔,要怎樣執(zhí)行呢?標(biāo)準(zhǔn)函數(shù)被調(diào)用后直接執(zhí)行苍凛,需要注意的是趣席,函數(shù)返回值是一個Result醇蝴,必須要用unwarp取其內(nèi)部包裹的值。
let retval = my_fn().unwrap();
println!("{:?}", retval);
由于Future調(diào)用在實(shí)際的執(zhí)行之前返回哑蔫,更準(zhǔn)確的說,返回的是一段在將來執(zhí)行的代碼闸迷。為了能夠在將來執(zhí)行,需要額外引入一種途徑腥沽,我們通過調(diào)用reactor的run方法來執(zhí)行Future。
let mut reactor = Core::new().unwrap();
let retval = reactor.run(my_fut()).unwrap();
println!("{:?}", retval);
注意返回值同樣需要unwrap今阳。
這樣看起來师溅,F(xiàn)uture并沒有想象中難墓臭,我們繼續(xù)。
級聯(lián)
級聯(lián)是Future最重要的特性之一妖谴∽靡。考慮一個很典型的場景:假設(shè)你向你的父母發(fā)email,通知他們一起吃晚飯窑多,收到確認(rèn)后,你會開始準(zhǔn)備晚餐洼滚,或者直接被拒絕埂息。這些事件之間是有依賴關(guān)系的的,只有當(dāng)前一個事件發(fā)生了遥巴,后一個事件才會啟動千康。這一系列有因果關(guān)系的事件就是級聯(lián)的概念,我們來看一個例子挪哄。
首先定義一個普通函數(shù)與一個Future吧秕。
fn my_fn_squared(i: u32) -> Result<u32, Box<Error>> {
Ok(i * i)
}
fn my_fut_squared(i: u32) -> impl Future<Item = u32, Error = Box<Error>> {
ok(i * i)
}
直接調(diào)用普通函數(shù):
let retval = my_fn().unwrap();
println!("{:?}", retval);
let retval2 = my_fn_squared(retval).unwrap();
println!("{:?}", retval2);
我們可以使用reactor來執(zhí)行同樣的代碼:
let mut reactor = Core::new().unwrap();
let retval = reactor.run(my_fut()).unwrap();
println!("{:?}", retval);
let retval2 = reactor.run(my_fut_squared(retval)).unwrap();
println!("{:?}", retval2);
但是對于Future而言,可以有更好的辦法迹炼。由于Future是trait,有一個and_then內(nèi)建方法颠毙,使用這個方法我們可以實(shí)現(xiàn)相同的語義斯入,但是不用顯式的創(chuàng)建以及調(diào)用兩次reactor run。
let chained_future = my_fut().and_then(|retval| my_fn_squared(retval));
let retval2 = reactor.run(chained_future).unwrap();
println!("{:?}", retval2);
看第一行蛀蜜,我們創(chuàng)建了一個Future刻两,叫做chained_future,同時(shí)包含了my_fut與my_fn_squared滴某。
這里有一個抖機(jī)靈的點(diǎn)磅摹,就是如何在不同的Future之間傳遞結(jié)果,rust通過閉包捕獲環(huán)境變量的方式傳遞Future返回的結(jié)果霎奢。
執(zhí)行過程如下:
1 調(diào)度并且執(zhí)行my_fut()
2 當(dāng)my_fut()執(zhí)行完成户誓,創(chuàng)建一個叫做retval的變量,并將my_fut()返回的結(jié)果保存至retval
3 將retval作為my_fn_squared(i: u32)的參數(shù)傳遞進(jìn)去幕侠,調(diào)度并執(zhí)行
4 把上述操作過程打包成一個名為chained_future的調(diào)用鏈帝美,chained_future也是一個Future。
第二行與上文類似晤硕,調(diào)用了reactor.run來執(zhí)行Future并返回結(jié)果悼潭。
通過這樣的方式可以把無限個Future打包成一個調(diào)用鏈,這樣的打包方式不會造成額外的開銷舞箍,所以不需要擔(dān)心性能問題舰褪。
rust的 borrow checked
特性可能會Future chain寫起來不是那么輕松,這種情況下可以嘗試在閉包參數(shù)中添加move
指令疏橄。
Future與普通函數(shù)混用
除了可以將Future級聯(lián)之外占拍,還可以將普通函數(shù)級聯(lián)至Future。這個特性很有用,因?yàn)椴⒉皇撬泻瘮?shù)都需要使用到Future特性刷喜。另一方面残制,也會出現(xiàn)在Future中調(diào)用外部函數(shù)的情況。如果這個函數(shù)返回類型不是Result掖疮,可以將這個函數(shù)簡單的添加在一個閉包中初茶。例如,假如有一個普通函數(shù):
fn fn_plain(i: u32) -> u32 {
i - 50
}
級聯(lián)后的Future如下所示:
let chained_future = my_fut().and_then(|retval| {
let retval2 = fn_plain(retval);
my_fut_squared(retval2)
});
let retval3 = reactor.run(chained_future).unwrap();
println!("{:?}", retval3);
如果函數(shù)返回Result
恼布,則有更好的辦法搁宾。我們來嘗試將my_fn_squared(i: u32) -> Result<u32, Box<Error>
方法打包進(jìn)Future chain。
在這里由于返回值是Result
所以無法調(diào)用and_then
, 但是Future有一個方法done()
可以將Result
轉(zhuǎn)換為impl Future
.這意味著我們可以將普通的函數(shù)通過done
方法把它包裝成一個Future爽待。
let chained_future = my_fut().and_then(|retval| {
done(my_fn_squared(retval)).and_then(|retval2| my_fut_squared(retval2))
});
let retval3 = reactor.run(chained_future).unwrap();
println!("{:?}", retval3);
注意第二行:done(my_fn_squared(retval))
翩腐,能夠這樣寫的原因是,我們將普通函數(shù)通過done
方法轉(zhuǎn)換成一個impl Future
茂卦。
現(xiàn)在我們不使用done
方法試試:
let chained_future = my_fut().and_then(|retval| {
my_fn_squared(retval).and_then(|retval2| my_fut_squared(retval2))
});
let retval3 = reactor.run(chained_future).unwrap();
println!("{:?}", retval3);
編譯不通過!
Compiling tst_fut2 v0.1.0 (file:///home/MINDFLAVOR/mindflavor/src/rust/tst_future_2)
error[E0308]: mismatched types
--> src/main.rs:136:50
|
136 | my_fn_squared(retval).and_then(|retval2| my_fut_squared(retval2))
| ^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::result::Result`, found anonymized type
|
= note: expected type `std::result::Result<_, std::boxed::Box<std::error::Error>>`
found type `impl futures::Future`
error: aborting due to previous error
error: Could not compile `tst_fut2`.
expected type std::result::Result<_, std::boxed::Box<std::error::Error>> found type impl futures::Future
处渣,這個錯誤有點(diǎn)讓人困惑。我們期望通過傳遞一個impl Future罐栈,但是我們用Result代替了暴备。我們將會在第二部分討論它。
范型
最后但并非最不重要的浅妆,F(xiàn)uture與范型一起工作不需要借助于任何黑魔法障癌。
看一個例子:
fn fut_generic_own<A>(a1: A, a2: A) -> impl Future<Item = A, Error = Box<Error>>
where
A: std::cmp::PartialOrd,
{
if a1 < a2 {
ok(a1)
} else {
ok(a2)
}
}
這個函數(shù)返回的是 a1 與 a2之間的較小的值,但是即便我們很確定這個函數(shù)沒有錯誤也需要給出Error
康辑,此外,返回值在這種情況下是小寫的ok
(原因是因?yàn)閛k是函數(shù)疮薇, 而不是enmu
)
現(xiàn)在我們執(zhí)行這個Future:
let future = fut_generic_own("Sampdoria", "Juventus");
let retval = reactor.run(future).unwrap();
println!("fut_generic_own == {}", retval);
閱讀到現(xiàn)在,你可能對Future應(yīng)該有所了解了迟隅,在這邊文章里励七,你可能注意到我沒有使用&
, 并且僅使用函數(shù)自身的值吼野。這是因?yàn)槭褂?code>impl Future后生命周期的行為會有差異两波,我將在下一篇文章中解釋如何使用它們。在下一篇文章中我們還會討論如何在Future chain中處理錯誤和使用await!()宏谚攒。