深入浅出 Rust Future - Part 2 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
krircc
V2EX    Rust

深入浅出 Rust Future - Part 2

  •  
  •   krircc 2018-12-03 22:45:32 +08:00 4454 次点击
    这是一个创建于 2557 天前的主题,其中的信息可能已经有所发展或是发生改变。

    译自Rust futures: an uneducated, short and hopefully not boring tutorial - Part 2时间:2018-12-03,译者: motecshine, 简介:motecshine

    欢迎向 Rust 中文社区投稿,投稿地址 ,好文将在以下地方直接展示

    1. Rust 中文社区首页

    2. Rust 中文社区 Rust 文章栏目

    3. 知乎专栏Rust 语言

    4. sf.gg 专栏Rust 语言

    5. 微博Rustlang-cn

    Intro

    在这个系列的第一篇文章我们了解了如何使用Rust Future.但是只有我们彻底的了解Future并且操作得当才能发挥它真正的作用。这个系列的第二篇文章,我们将介绍如何避免Future里常见的陷阱。

    Error troubles

    我们将Future组织成一个很简单,只要通过Rust Future提供的and_then函数就可以了。但是在上一篇文章中我们使用了Box<Error> trait作为错误类型,绕过了编译器的检查。为什么我们没有使用更为详细的错误类型?原因很简单, 每个Future函数的错误返回都有可能不同.

    原则 1: 当我们将不同的Future组织成一个调用时,每个Future都应该返回相同的Error Type.

    让我们一起来证明一下这一点.

    我们有两个被叫做ErrorAErrorBError类型, 我们将会实现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中使用ErrorAErrorB.

    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 

    到现在为止还挺好的,让我们把ErrorAErrorB打包成一个调用链:

    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,所以我们只需要在ErrorAErrorB之间调用这个函数就行了.

    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 ErrorBFrom<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(ProjectonTy { 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。 我们还将从头开始编写未来的实现结构。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2860 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 42ms UTC 00:28 PVG 08:28 LAX 16:28 JFK 19:28
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86