【译】理解Rust中的Futures(二)

原文标题:Understanding Futures in Rust -- Part 2
原文链接:https://www.viget.com/articles/understanding-futures-is-rust-part-2/
公众号: Rust 碎碎念
翻译 by: Praying

背景

如果你还没有看前面的内容,可以在这里[1]查看(译注:已有译文,可在公众号查看)。

在第一部分,我们介绍了 Future trait,了解了 future 是如何被创建和运行的,并且开始知道它们如何能被链接到一起。

上次内容的代码可以在这个 playground 链接[2]查看,并且本文中所有示例代码将会以这段代码为基础。

注意:所有的代码示例都有对应的 playground 链接,其中一些用于解释说明但无法编译的代码会有相应的标记。

目标

如果你熟悉 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]

这里唯一需要注意的就是,只有将模块,类型和函数公开,才能在代码中使用它们。这可以通过pub关键字来完成。

工具函数实现 Future::Ready

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 将会报错。

BLOCK_ON

为了我们的目标,我们把我们的 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]

组合子(Combinators)

首先,让我们从一些能够让你直接作用于另一个 Future 的Output值的一些组合子开始。在本文中,我们使用非正式的但是比较流行的组合子定义,即能够允许你对某种类型执行操作,并与其他类型结合起来的函数。例如,一个嵌套的 future 可以由一个组合子函数函数创建,它可以有一个复杂的类型Future< Output = Future < Output = i32>>。这可以被称为一个 future,该 future 的输出(Output)是另一个 future,新的 future 的输出是 i32 类型。这样的组合子中,最简单的一个就是map。

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>(selfSized, f: F) -> Map<Self, F>
where
    F: FnOnce(Self::Output) -> U,
    SelfSized,

map是一个泛型函数,它接收一个闭包,返回一个实现了 Future 的Map结构体。不是每当我们在值上进行链接都需要实现Futuretrait,正如我们在最后一部分做的那样,我们可以使用这些函数来为我们完成这些工作。

让我们来分析一下:

Map<Self, F>声明了 map 函数的(返回)类型,包括当前的 future,以及传入函数的 future。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypjwd.html