深入浅出Rust Future - Part 1 原

栏目: 编程语言 · Rust · 发布时间: 5年前

内容简介:本文译自欢迎向Rust中文社区投稿,如果你是一个程序员并且也喜欢Rust这门语言, 那么你应该经常在社区听到讨论

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

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

  1. Rust中文社区首页

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

  3. 知乎专栏 Rust语言

  4. sf.gg专栏 Rust语言

  5. 微博Rustlang-cn

Intro

如果你是一个 程序员 并且也喜欢Rust这门语言, 那么你应该经常在社区听到讨论 Future 这个库的声音, 一些很优秀的 Rust Crates 都使用了 Future 所以我们也应该对它有足够的了解并且使用它. 但是大多数程序员很难理解 Future 到底是怎么工作的, 当然有官方 Crichton's tutorial 这样的教程, 虽然很完善, 但我还是很难理解并把它付诸实践.

我猜测我并不是唯一一个遇到这样问题的程序员, 所以我将分享我自己的最佳实践, 希望这能帮助你理解这个话题.

Futures in a nutshell

Future 是一个不会立即执行的特殊 functions . 他会在将来执行(这也是他被命名为 future 的原因).我们有很多理由让 future functions 来替代 std functions ,例如: 优雅性能可组合性 . future 的缺点也很明显: 很难用代码去实现. 当你不知道何时会执行某个函数时, 你又怎么能理解他们之间的因果关系呢?

处于这个原因, Rust会试图帮助我们这些菜鸟程序员去理解和使用 future 这个特性。

Rust's futures

Rust 的 futures 总是一个 Results : 这意味着你必须同时指定期待的返回值和备用的错误类型。 让我们先简单的实现一个方法,然后把它改造成 future . 我们设计的这个方法返回值是 u32 或者是一个 被 Box 包围着的 Error trait , 代码如下所示:

fn my_fn() -> Result<u32, Box<Error>> { 
    Ok(100) 
}

这段代码很简单,看起来并没有涉及到 future . 接下来让我们看看下面的代码:

fn my_fut() -> impl Future<Item = u32, Error = Box<Error>> { 
    ok(100) 
}

注意这两段代码不同的地方:

  1. 返回的类型不再是 Result 而是一个 impl Future . Rust Nightly 版本是允许我们返回一个 future 的。

  2. 第二个函数返回值的参量 Item = u32, Error = Box<Error> 较第一个函数来看更加详细明确。

为了能让第二段代码工作 你需要使用拥有 conservative_impl_trait 特性的 nightly 版本。当然,如果不嫌麻烦,你可以使用 boxed trait 来替代。

另请注意第一个函数返回值使用的是大写的 Ok(100) 。 在 Result 函数中,我们使用大写的 Ok 枚举,而 future 我们使用小写的ok方法.

规则: 在Rust future 中使用小写返回方法 ok(100) .

好了现在我们改造完毕了,但是我们该怎样执行第二个我们改造好的方法?标准方法我们可以直接调用,但是这里需要注意的是地一个方法返回值是一个 Result , 所以我们需要使用 unwrap() 来获取我们期待的值。

let retval = my_fn().unwrap(); 
println!("{:?}", retval);

由于 future 在实际执行之前返回(或者更准确的说, 返回的是我们将来要执行的代码), 我们需要一种途径去执行 future 。为此我们使用 Reactor 。我们只需要创建一个 Reactor 并且调用他的 run 方法就可以执行 future . 就像下面的代码:

let mut reactor = Core::new().unwrap(); 
let retval = reactor.run(my_fut()).unwrap(); 
println!("{:?}", retval);

注意这里我们 unwrap 的是 run 方法,而不是 my_fut . 看起来真的很简单。

Chaining

future 一个很重要的特性就是能够把其他的 future 组织起来形成一个 chain . 举个栗子:

你邀请你的父母一起吃晚饭通过email. 你在电脑前等待他们的回复 父母同意与你一起吃晚饭(或者因为一些原因拒绝了)。

Chaining 就是这样的,让我们看一个简单的例子:

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) 
}

现在我们可以使用下面的方式去调用这两个函数:

let retval = my_fn().unwrap(); 
println!("{:?}", retval); 
let retval2 = my_fn_squared(retval).unwrap(); 
println!("{:?}", retval2);

当然我们也可以模拟 Reactor 来执行相同的代码:

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);

但还有更好的方法,在Rust中 future 也是一个 trait 他有很多种方法(这里我们会介绍些),其中一个名为 and_then 的方法,在语义上完全符合我们最后写的代码片段。但是没有显式的执行 Reactor Run , 让我们一起来看看下面的代码:

let chained_future = my_fut().and_then(|retval| my_fut_squared(retval));
let retval2 = reactor.run(chained_future).unwrap(); 
println!("{:?}", retval2);

让我们看看第一行:创建一个被叫做 chained_futurefuture , 它把 my_futmu_fut_squared``future 串联了起来。 这里让人难以理解的部分是: 我们如何将上一个 future 的结果传递给下一个 future ?

在Rust中我们可以通过闭包来捕获外部变量来传递 future 的值。 可以这样想:

  1. 调度并且执行 my_fut()
  2. my_fut() 执行完毕后,创建一个 retval 变量并且将 my_fut() 的返回值存到其中。
  3. 现在将 retval 作为 my_fn_squared(i: u32) 的参数传递进去,并且调度执行 my_fn_squared
  4. 把上面一些列的操作打包成一个名为 chained_future 的调用链。

第二行代码,与之前的相同: 我们调用 Reactor run() , 要求执行 chained_future 并给出结果。 当然我们可以通过这种方式将无数个 future 打包成一个 chain , 不要去担心性能问题, 因为 future chainzero cost .

RUST borrow checked 可能让你的 future chain 写起来不是那么的轻松,所以你可以尝试 move 你的参数变量.

Mixing futures and plain functions

你也可以使用普通的函数来做 future chain , 这很有用, 因为不是每个功能都需要使用 future . 此外, 你也有可能希望调用外部你无法控制的函数。 如果函数没有返回Result,你只需在闭包中添加函数调用即可。 例如,如果我们有这个普通函数:

fn fn_plain(i: u32) -> u32 { 
    i - 50  
} 

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);

如果你的函数返回 Result 则有更好的办法。我们一起来尝试将 my_fn_squared(i: u32) -> Result<u32, Box<Error> 方法打包进 future chain

在这里由于返回值是 Result 所以你无法调用 and_then , 但是 future 有一个方法 done() 可以将 Result 转换为 impl Future .这意味着我们可以将普通的函数通过 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)) 允许我们在链式调用的原因是:我们将普通函数通过 done 方法转换成一个 impl Future . 现在我们不使用 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 ,这个错误有点让人困惑. 我们将会在第二部分讨论它。

Generics

最后但并非最不重要的, futuregeneric (这是啥玩意儿啊)一起工作不需要任何黑魔法.

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) } 
}

这个函数返回的是 a1 与 a2之间的较小的值。但是即便我们很确定这个函数没有错误也需要给出 Error ,此外,返回值在这种情况下是小写的 ok (原因是函数, 而不是 enmu )

现在我们调用这个 future :

let future = fut_generic_own("Sampdoria", "Juventus"); 
let retval = reactor.run(future).unwrap(); 
println!("fut_generic_own == {}", retval);

阅读到现在你可能对 future 应该有所了解了, 在这边文章里你可能注意到我没有使用 & , 并且仅使用函数自身的值。这是因为使用 impl Future ,生命周期的行为并不相同,我将在下一篇文章中解释如何使用它们。在下一篇文章中我们也会讨论如何在 future chain 处理错误和使用await!()宏。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

学习bash

学习bash

Cameron Newham、Bill Rosenblatt / 徐炎、查石祥 / 机械工业出版社 / 2003-1-1 / 45.00

bash是自由软件基金会发布的“Bourne Again Shell”的缩写。它是流行的UNIX Bourne shell的免费可用替代产品,供全球Linux用户选用。《学习bash》正是bash的权威指南。 无论你是对bash的用户界面感兴趣,还是对其强大的编程能力感兴趣,你都会发现本书很有价值。它教授了如何使用bash的高级命令行特性,如命令历史、命令行编辑和命令完成。 本书还......一起来看看 《学习bash》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器