本文译自Rust futures: an uneducated, short and hopefully not boring tutorial - Part 1,时间:2018-12-02,译者: motecshine, 简介:motecshine
欢迎向 Rust 中文社区投稿,投稿地址,好文将在以下地方直接展示
Rust 中文社区 Rust 文章栏目
知乎专栏Rust 语言
如果你是一个程序员并且也喜欢 Rust 这门语言, 那么你应该经常在社区听到讨论Future
这个库的声音, 一些很优秀的Rust Crates
都使用了Future
所以我们也应该对它有足够的了解并且使用它. 但是大多数程序员很难理解Future
到底是怎么工作的, 当然有官方 Crichton's tutorial
这样的教程, 虽然很完善, 但我还是很难理解并把它付诸实践.
我猜测我并不是唯一一个遇到这样问题的程序员, 所以我将分享我自己的最佳实践, 希望这能帮助你理解这个话题.
Future
是一个不会立即执行的特殊functions
. 他会在将来执行(这也是他被命名为future
的原因).我们有很多理由让future functions
来替代std functions
,例如: 优雅
,性能
,可组合性
.future
的缺点也很明显: 很难用代码去实现. 当你不知道何时会执行某个函数时, 你又怎么能理解他们之间的因果关系呢?
处于这个原因,Rust 会试图帮助我们这些菜鸟程序员去理解和使用future
这个特性。
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)
}
注意这两段代码不同的地方:
返回的类型不再是Result
而是一个impl Future
. Rust Nightly
版本是允许我们返回一个future
的。
第二个函数返回值的参量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
. 看起来真的很简单。
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_future
的future
, 它把my_fut
与mu_fut_squared``future
串联了起来。 这里让人难以理解的部分是: 我们如何将上一个future
的结果传递给下一个future
?
在 Rust 中我们可以通过闭包来捕获外部变量来传递
future
的值。 可以这样想:
my_fut()
my_fut()
执行完毕后,创建一个retval
变量并且将my_fut()
的返回值存到其中。retval
作为my_fn_squared(i: u32)
的参数传递进去,并且调度执行my_fn_squared
。chained_future
的调用链。第二行代码,与之前的相同: 我们调用Reactor run()
, 要求执行chained_future
并给出结果。 当然我们可以通过这种方式将无数个future
打包成一个chain
, 不要去担心性能问题, 因为future chain
是 zero cost
.
RUST
borrow checked
可能让你的future chain
写起来不是那么的轻松,所以你可以尝试move
你的参数变量.
你也可以使用普通的函数来做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
,这个错误有点让人困惑. 我们将会在第二部分讨论它。
最后但并非最不重要的, future
与 generic
(这是啥玩意儿啊)一起工作不需要任何黑魔法.
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!()宏。