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 compilefn 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。总体来讲,这不是个安全的假设。
Result 组合子在 futures-rs 库的 0.1 版本中,Future trait 和Result类型紧密关联。Future trait 的定义如下:
// does not compiletrait 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类型。下面是一些能够阐述这个概念的实现。
AndThen让我们回顾之前想象到的 API 函数。假定它们现在处于会发生网络分区和服务器中断的现实世界中,不会总是能返回一个值。这些 API 方法实际上会返回一个嵌有 result 的 future 以表明它已经完成,并且是要么是成功完成,要么是带有错误的完成。我们需要去处理这些结果,下面是我们可能是根据现有工具处理它的方式。
// does not compilefn 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 中