原文标题:Understanding Futures in Rust -- Part 2
原文连接:https://www.viget.com/articles/understanding-futures-is-rust-part-2/
公众号: Rust 碎碎念
翻译 by: Prayinghtml
若是你尚未看前面的内容,能够在这里[1]查看(译注:已有译文,可在公众号查看)。web
在第一部分,咱们介绍了 Future trait,了解了 future 是如何被建立和运行的,而且开始知道它们如何能被连接到一块儿。
api
上次内容的代码能够在这个 playground 连接[2]查看,而且本文中全部示例代码将会以这段代码为基础。
数组
注意:全部的代码示例都有对应的 playground 连接,其中一些用于解释说明但没法编译的代码会有相应的标记。promise
若是你熟悉 JavaScript 中的 promise 而且阅读了最新的博客,你可能会对先前文章中提到的组合子(then
、catch
和finally
)感到困惑。
安全
你将会在本文章找到与它们对等的东西,而且在最后,下面这段代码将可以编译。你将会理解使得 future 可以运做的类型,trait 和底层概念。服务器
// This does not compile, yet
fn main() {
let my_future = future::ready(1)
.map(|x| x + 3)
.map(Ok)
.map_err(|e: ()| format!("Error: {:?}", e))
.and_then(|x| future::ready(Ok(x - 3)))
.then(|res| {
future::ready(match res {
Ok(val) => Ok(val + 3),
err => err,
})
});
let val = block_on(my_future);
assert_eq!(val, Ok(4));
}
首先,咱们须要一些工具函数,future::ready
和block_on
。这些函数可以让咱们很容易地建立和运行 future 直到它们完成,这些函数虽然有用,可是在生产环境的代码中并不常见。
网络
在开始以前,咱们先把咱们的Future
trait 和Context
结构体整合到模块里以避免和标准库冲突。闭包
mod task {
use crate::NOTIFY;
pub struct Context<'a> {
waker: &'a Waker,
}
impl<'a> Context<'a> {
pub fn from_waker(waker: &'a Waker) -> Self {
Context { waker }
}
pub fn waker(&self) -> &'a Waker {
&self.waker
}
}
pub struct Waker;
impl Waker {
pub fn wake(&self) {
NOTIFY.with(|f| *f.borrow_mut() = true)
}
}
}
use crate::task::*;
mod future {
use crate::task::*;
pub enum Poll<T> {
Ready(T),
Pending,
}
pub trait Future {
type Output;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output>;
}
}
use crate::future::*;
Playground 连接[3]app
这里惟一须要注意的就是,只有将模块,类型和函数公开,才能在代码中使用它们。这能够经过pub
关键字来完成。
future::ready
建立了一个 future,该 future 带有传入值而且是当即就绪(ready)的。当你有一个已经不是 future 的值的时候,这个函数能够用于开启一个 future 链,就像前一个示例那样。
mod future {
// ...
pub struct Ready<T>(Option<T>);
impl<T> Future for Ready<T> {
type Output = T;
fn poll(&mut self, _: &Context) -> Poll<Self::Output> {
Poll::Ready(self.0.take().unwrap())
}
}
pub fn ready<T>(val: T) -> Ready<T> {
Ready(Some(val))
}
}
fn main() {
let my_future = future::ready(1);
println!("Output: {}", run(my_future));
}
Playground 连接[4]
咱们建立了一个类型为Ready<T>
的泛型结构体,该结构体包装了一个Option
。这里咱们使用Option
枚举以保证 poll 函数只被调用一次。在 executor 的实现中,在返回一个Poll::Ready
以后调用 poll 将会报错。
为了咱们的目标,咱们把咱们的 run 函数重命名为block_on
。在future-preview
这个 crate 中,该函数使用内部的LocalPool
来运行一个 future 直到完成,同时会阻塞当前线程。咱们的函数也作了类似的事情。
fn block_on<F>(mut f: F) -> F::Output
where
F: Future,
{
NOTIFY.with(|n| loop {
if *n.borrow() {
*n.borrow_mut() = false;
let ctx = Context::from_waker(&Waker);
if let Poll::Ready(val) = f.poll(&ctx) {
return val;
}
}
})
}
fn main() {
let my_future = future::ready(1);
println!("Output: {}", block_on(my_future));
}
Playground 连接[5]
首先,让咱们从一些可以让你直接做用于另外一个 Future 的Output
值的一些组合子开始。在本文中,咱们使用非正式的可是比较流行的组合子定义,即可以容许你对某种类型执行操做,并与其余类型结合起来的函数。例如,一个嵌套的 future 能够由一个组合子函数函数建立,它能够有一个复杂的类型Future< Output = Future < Output = i32>>
。这能够被称为一个 future,该 future 的输出(Output)是另外一个 future,新的 future 的输出是 i32 类型。这样的组合子中,最简单的一个就是map
。
若是你熟悉Result
或者Option
类型的map
函数,那么对它应该不陌生。map 组合子持有一个函数并将其应用到 future 的Output
值上,返回一个新的 future,这个新 future 把函数的结果(Result)做为它的Output
。Future 中的 map 组合子甚至比Result
或者Option
中更简单,由于不须要考虑 failure 的状况。map 就是简单的Future->Future
。
下面是函数签名:
// does not compile
fn map<U, F>(self: Sized, f: F) -> Map<Self, F>
where
F: FnOnce(Self::Output) -> U,
Self: Sized,
map
是一个泛型函数,它接收一个闭包,返回一个实现了 Future 的Map
结构体。不是每当咱们在值上进行连接都须要实现Future
trait,正如咱们在最后一部分作的那样,咱们可使用这些函数来为咱们完成这些工做。
让咱们来分析一下:
Map<Self, F>
声明了 map 函数的(返回)类型,包括当前的 future,以及传入函数的 future。
where
是一个可以让咱们添加类型约束的关键字。对于F
类型参数,咱们能够在内部定义约束map<U, F: FnOnce(Self::Output) -> U
,可是使用 where 语句可读性会更好。
FnOnce(Self::Output) -> U
是一个函数的类型定义,该函数接收当前类型的Output
并返回任意类型U
。FnOnce
是函数 trait 中的一个,其余还包括FnMut
和Fn
。FnOnce
是用起来最简单的,由于编译器能够保证这个函数只被调用一次。它使用环境中用到的值并获取其全部权。Fn
和FnMut
分别以不可变和可变的方式借用环境中值的引用。全部的闭包都实现了FnOnce
trait,而且其中一些没有移动值的闭包还实现了FnMut
和Fn
trait。这是 Rust 作的最酷的事情之一,容许对闭包和第一类函数参数进行真正有表达力的使用。Rust book 中的相关内容[6]值得一读。
Self: Sized
是一个约束,容许map
只能被Sized
的 trait 实现者调用。你没必要考虑这个问题,可是确实有些类型不是Sized
。例如,[i32]
是一个不肯定大小的数组。由于咱们不知道它多长。若是咱们想要为它实现咱们的Future
trait,那么咱们就不能对它调用map
。
大多数组合子都遵循这个模式,所以接下来的文章咱们就不须要分析的这么仔细了。
下面是一个map
的完整实现,它的Map
类型以及它对Future
的实现
mod future {
trait Future {
// ...
fn map<U, F>(self, f: F) -> Map<Self, F>
where
F: FnOnce(Self::Output) -> U,
Self: Sized,
{
Map {
future: self,
f: Some(f),
}
}
}
// ...
pub struct Map<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, F, T> Future for Map<Fut, F>
where
Fut: Future,
F: FnOnce(Fut::Output) -> T,
{
type Output = T;
fn poll(&mut self, cx: &Context) -> Poll<T> {
match self.future.poll(cx) {
Poll::Ready(val) => {
let f = self.f.take().unwrap();
Poll::Ready(f(val))
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1).map(|val| val + 1);
println!("Output: {}", block_on(my_future));
}
Playground 连接[7]
从高层次来说,当咱们调用一个 future 上的map
时,咱们构造了一个Map
类型,该类型持有当前 future 的引用以及咱们传入的闭包。Map
对象自身也是一个 Future。当它被轮询时,它依次轮询底层的 future。当底层的 future 就绪后,它获取那个 future 的Output
的值而且把它传入闭包,对Poll::Ready
中的闭包返回的值进行包装(wrapping)而且把新值向上传递。
若是你阅读了最新的博客,你对在这里看到的东西应该感到很熟悉,可是在咱们继续以前,我会快速地讲解做为一个复习。
pub struct Map<Fut, F>
是一个关于 future——Fut
和函数F
的泛型。
f: Option<F>
是一个包装了闭包了Option
类型。这里是个小技巧,以保证闭包只被调用一次。当你获取一个Option
的值,它会用None
替换内部的值而且返回里面包含的值。若是在返回一个Poll::Ready
以后被轮询,这个函数会 panic。在实际中,future 的 executor 不会容许这种状况发生。
type Output = T;
定义了 map future 的输出和咱们的闭包的返回值是将会是相同的。
Poll::Read(f(val))
返回带有闭包返回结果的就绪(ready)状态。
Poll::Pending => Poll::Pending
若是底层的 future 返回 pending,继续传递。
future::ready(1).map(|val| val + 1);
这对就绪(ready)future 的输出进行了 map,并对其加 1。它返回了一个 map future,其中带有对原先的 future 的一个引用。map future 在运行期间轮询原先的 future 是否就绪(ready)。这和咱们的AddOneFuture
作的是相同的事情。
这真的很酷,主要有如下几个缘由。首先,你没必要对每个你想要进行的计算都实现一个新的 future,它们能够被包装(wrap)进组合子。事实上,除非你正在实现你本身的异步操做,不然你可能历来都不须要本身去实现Future
trait。
如今咱们有了map
,咱们能够把任何咱们想要的计算连接起来,对么?答案是对的,可是对此还有一个至关大的警告。
想象一下,当你有一些函数,这些函数返回你想要连接起来的 future。对于这个例子,咱们能够想象,它们是下面的 api 调用,这些调用返回包装(wrap)在 future 中的结果,get_user
和get_files_for_user
。
// does not compile
fn main() {
let files_future = get_user(1).map(|user| get_files_for_user(user));
println!("User Files: {}", block_on(files_future));
}
这段代码没法编译,可是你能够想象你在这里构建的类型,看起来应该像这样:Future<Output = Future<Output= FileList>>
。这在使用Result
和Option
类型的时候也是一个常见问题。使用map
函数常常会致使嵌套的输出和对这些嵌套的繁琐处理。在这种状况下,你不得不去跟踪到底嵌套了多少层而且对每个嵌套的 future 都调用block_on
。
幸运地是,Result
,Option
有一个被称为and_then
的解决方案。Option
的and_then
经过对T
应用一个函数来映射(map)Some(T) -> Some(U)
,而且返回闭包所返回的Option
。对于 future,它是经过一个称为then
的函数来实现的,该函数看起来很像映射(map),可是这个闭包应该它本身的 future。在一些语言中,这被称为flatmap
。这里值得注意的是,传递给then
的闭包返回的值必须是实现了Future
,不然你将会获得一个编译器错误。
这里是咱们的对于then
,Then
结构体和它的对Future
trait 的实现。其中的大部份内容和咱们在 map 中作的很像。
mod future {
trait Future {
// ...
fn then<Fut, F>(self, f: F) -> Then<Self, F>
where
F: FnOnce(Self::Output) -> Fut,
Fut: Future,
Self: Sized,
{
Then {
future: self,
f: Some(f),
}
}
}
// ...
pub struct Then<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, NextFut, F> Future for Then<Fut, F>
where
Fut: Future,
NextFut: Future,
F: FnOnce(Fut::Output) -> NextFut,
{
type Output = NextFut::Output;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
match self.future.poll(cx) {
Poll::Ready(val) => {
let f = self.f.take().unwrap();
f(val).poll(cx)
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1)
.map(|val| val + 1)
.then(|val| future::ready(val + 1));
println!("Output: {}", block_on(my_future));
}
Playground 连接[8]
这里面没见过的代码多是f(val).poll(cx)
。它调用了带有先前 future 的闭包而且直接返回给你poll
的值。
聪明的你可能会意识到,咱们的Then::poll
函数可能会 panic。若是第一个 future 返回就绪(ready)可是第二个 future 返回Poll::Pending
,接着let f = self.f.take().unwrap();
这行代码就会在下次被轮询(poll)的时候 panic 并退出程序。在future-preview
中,这种状况会经过一个称为Chain[9]的类型来处理。Chain 经过 unsafe 代码块来实现,而且使用了新类型——Pin
。这些内容超出了本文的范围。目前来说,咱们能够假定任何经过then
闭包返回的 future 都毫不会返回Poll::Pending
。整体来说,这不是个安全的假设。
在 futures-rs 库的 0.1 版本中,Future
trait 和Result
类型紧密关联。Future
trait 的定义以下:
// does not compile
trait Future {
type Item;
type Error;
fn poll(self) -> Poll<Self::Item, Self::Error>;
}
Poll
类型里定义了成功状态、失败状态和未就绪状态。这意味着像map
这种函数只有当 Poll 是就绪而且不是错误的状况下才能执行。尽管这会产生一些困扰,可是它在连接组合子而且根据成功或失败状态作决定的时候,会产生一些很是好的人体工程学(ergonomics )。
这与std::future
的实现方式有所不一样。如今 future 要么是就绪或者是未就绪,对于成功或失败语义是不可知的。它们能够包含任何值,包括一个Result
。为了获得便利的组合子,好比像map_err
可以让你只改变一个嵌套的 Result 中的错误类型,或者想and_then
这样,容许你只改变嵌套 Result 中的值类型,咱们须要实现一个新的 trait。下面是TryFuture
的定义:
mod future {
//...
pub trait TryFuture {
type Ok;
type Error;
fn try_poll(self, cx: &mut Context) -> Poll<Result<Self::Ok, Self::Error>>;
}
impl<F, T, E> TryFuture for F
where
F: Future<Output = Result<T, E>>,
{
type Ok = T;
type Error = E;
fn try_poll(&mut self, cx: &Context) -> Poll<F::Output> {
self.poll(cx)
}
}
}
Playground 连接[10]
TryFuture
是一个 trait,咱们能够为任意的类型<F, T, E>
实现这个 trait,其中F
实现了Future
trait,它的Output
类型是Result<T,E>
。它只有一个实现者。那个实现者定义了一个try_poll
函数,该函数与Future
trait 上的poll
有相同的签名,它只是简单地调用了poll
方法。
这意味着任何一个拥有 Result 的Output
类型的 future 也可以访问它的成功/错误(success/error)状态。这也使得咱们可以定义一些很是方便的组合子来处理这些内部 Result 类型,而没必要在一个map
或and_then
组合子内显示地匹配Ok
和Err
类型。下面是一些可以阐述这个概念的实现。
让咱们回顾以前想象到的 API 函数。假定它们如今处于会发生网络分区和服务器中断的现实世界中,不会老是能返回一个值。这些 API 方法实际上会返回一个嵌有 result 的 future 以代表它已经完成,而且是要么是成功完成,要么是带有错误的完成。咱们须要去处理这些结果,下面是咱们多是根据现有工具处理它的方式。
// does not compile
fn main() {
let files_future = get_user(1).then(|result| {
match result {
Ok(user) => get_files_for_user(user),
Err(err) => future::ready(Err(err)),
}
});
match block_on(files_future) {
Ok(files) => println!("User Files: {}", files),
Err(err) => println!("There was an error: {}", err),:w
};
}
状况还不算太坏,可是假定你想要连接更多的 future,事情很快就会变得一团糟。幸运的是,咱们能够定义一个组合子——and_then
,该组合子将会把类型Future<Output = Result<T, E>>
映射到Future<Output = Result<U, E>>
,其中咱们把T
变为了U
。
下面是咱们定义它的方式:
mod future {
pub trait TryFuture {
// ...
fn and_then<Fut, F>(self, f: F) -> AndThen<Self, F>
where
F: FnOnce(Self::Ok) -> Fut,
Fut: Future,
Self: Sized,
{
AndThen {
future: self,
f: Some(f),
}
}
}
// ...
pub struct AndThen<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, NextFut, F> Future for AndThen<Fut, F>
where
Fut: TryFuture,
NextFut: TryFuture<Error = Fut::Error>,
F: FnOnce(Fut::Ok) -> NextFut,
{
type Output = Result<NextFut::Ok, Fut::Error>;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
match self.future.try_poll(cx) {
Poll::Ready(Ok(val)) => {
let f = self.f.take().unwrap();
f(val).try_poll(cx)
}
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1)
.map(|val| val + 1)
.then(|val| future::ready(val + 1))
.map(Ok::<i32, ()>)
.and_then(|val| future::ready(Ok(val + 1)));
println!("Output: {:?}", block_on(my_future));
}
Playground 连接[11]
你对此应该较为熟悉。事实上,这和then
组合子的实现基本一致。只有一些关键的区别须要注意:
函数定义在 TryFuture trait 中
type Output = Result<NextFut::Ok, Fut::Error>;
代表 AndThen future 的输出拥有新的 future 的值类型,以及在它以前的 future 的错误类型。换句话说,若是先前的 future 的输出包含一个错误类型,那么这个闭包将不会被执行。
咱们调用的是try_poll
而不是poll
。
值得注意的是,当你像这样来连接组合子的时候,它们的类型前面可能会变得很长且在编译错误信息中难以阅读。and_then
函数要求 future 调用时的错误类型和由闭包返回的类型必须是相同的。
回到咱们的想象的 api 调用。假定调用的 api 都返回带有同一类错误的 future,可是你须要在调用之间进行额外的步骤。假定你必须解析第一个 api 结果真后把它传递给第二个。
// 没法编译
fn main() {
let files_future = get_user(1)
.and_then(|user_string| parse::<User>())
.and_then(|user| get_files_for_user(user));
match block_on(files_future) {
Ok(files) => println!("User Files: {}", files),
Err(err) => println!("There was an error: {}", err),:w
};
}
这看起来很好,可是没法编译,而且会有个晦涩的错误信息说它指望获得像ApiError
的东西可是却找到了一个ParseError
。你能够在解析返回的Result
上使用过map_err
组合子,可是对于 future 应该如何处理呢?若是咱们为 TryFuture 实现一个map_err
,那么咱们能够重写成下面这样:
// 没法编译
fn main() {
let files_future = get_user(1)
.map_err(|e| format!("Api Error: {}", e))
.and_then(|user_string| parse::<User>())
.map_err(|e| format!("Parse Error: {}", e))
.and_then(|user| get_files_for_user(user))
.map_err(|e| format!("Api Error: {}", e));
match block_on(files_future) {
Ok(files) => println!("User Files: {}", files),
Err(err) => println!("There was an error: {}", err),:w
};
}
若是这让你看着比较混乱,请继续关注本系列的第三部分,我将谈谈如何处理这个问题和你可能会在使用 future 时遇到的其余问题。
下面是咱们实现map_err
的方式
mod future {
pub trait TryFuture {
// ...
fn map_err<E, F>(self, f: F) -> MapErr<Self, F>
where
F: FnOnce(Self::Error) -> E,
Self: Sized,
{
MapErr {
future: self,
f: Some(f),
}
}
}
// ...
pub struct MapErr<Fut, F> {
future: Fut,
f: Option<F>,
}
impl<Fut, F, E> Future for MapErr<Fut, F>
where
Fut: TryFuture,
F: FnOnce(Fut::Error) -> E,
{
type Output = Result<Fut::Ok, E>;
fn poll(&mut self, cx: &Context) -> Poll<Self::Output> {
match self.future.try_poll(cx) {
Poll::Ready(result) => {
let f = self.f.take().unwrap();
Poll::Ready(result.map_err(f))
}
Poll::Pending => Poll::Pending,
}
}
}
}
fn main() {
let my_future = future::ready(1)
.map(|val| val + 1)
.then(|val| future::ready(val + 1))
.map(Ok)
.and_then(|val| future::ready(Ok(val + 1)))
.map_err(|_: ()| 5);
println!("Output: {:?}", block_on(my_future));
}
Playground 连接[12]
惟一比较陌生的地方是Poll::Ready(result.map_err(f))
。在这段代码里,咱们传递咱们的闭包到Result
类型的map_err
函数里。
如今,文章开头的代码能够运行了!比较酷的是这些全都是咱们本身实现的。还有不少其余用途的组合子,可是它们几乎都是相同的方式构建的。读者能够本身练习一下,试试实现一个map_ok
组合子,行为相似于TryFuture
上的map_err
可是适用于成功的结果。
Playground 连接[13]
Rust 中的 Future 之因此如此强大,是由于有一套能够用于连接计算和异步调用的组合子。
咱们也学习了 Rust 强大的函数指针 trait,FnOnce
,FnMut
和Fn
。
咱们已经了解了如何使用嵌入在 future 中的 Result 类型。
在第三部分中,咱们将会介绍使错误处理没有那么痛苦的方式,当你有不少分支时,如何处理返回的 future,以及咱们将深刻到 async/await 这个使人激动的世界。
这里: https://www.viget.com/articles/understanding-futures-in-rust-part-1/
[2]这个playground连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c354bc3ffaf4cbb5502e839f96459023
[3]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6cebb88919bd65411178ce8019a3aa06
[4]Playground 连接: https://www.viget.com/articles/understanding-futures-is-rust-part-2/
[5]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9e7fca1f3c6f2f5f91b25622db71635f
[6]Rust book中的相关内容: https://doc.rust-lang.org/book/ch13-01-closures.html
[7]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9c427527c64b4dd5238c508de1d4151a
[8]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d86c1223ed4318dcbfa3539ca9a021f2
[9]Chain: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/stream/trait.StreamExt.html#method.chain
[10]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=78daa6a5e60df17d8334199c43fe1e36
[11]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=71fe0962974657f6b9be25510a652b3d
[12]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f9a6cc9cddaac1a43a85bc24db436964
[13]Playground 连接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=92a88fffb74ad350a4db1970b646c41f