Rust修行之Future篇-part1

本文翻译自Rust futures: an uneducated, short and hopefully not boring tutorial-part1

介绍

既然能够看到这个页面,想必各位看官至少应该是一个程序员,并且很有可能是中意这门新语言的。如果各位看官还有爱逛论坛或者社区的习惯,或多或少可能看到过关于Future的讨论。大量优秀的项目已经改由Future重构底层实现,比如Hyper,并且这种转变正在成为趋势,如果对rust感兴趣,Future是一个不容错过的特性。尽管受到大量的关注,但不能否认的是,Future是一个很难理解的概念,如果各位看官对自己的英文水平与rust基础很自信,可以直接看官方的Crichton's tutorial,但是对于像我这样的不太自信的选手,我认为官方文档并不适合作为学习教材。

我猜想我可能并不是唯一的一个不太自信的选手,所以我把我的一些思考和实践记录下来,希望这个文档能帮到大家。

概要

Future的本质特征就是其字面意思,指一系列不会立即执行,但是可能在未来某个时间执行的函数。标准函数一旦被调用将会立即执行,为什么要引入Future这样的概念呢?原因很多,包括性能,架构的优雅性,可扩展性等。Future的缺点也十分明显,即很难用代码实现。通常在一个线程内,函数是串行执行的,但是当不确定一个函数会在什么时候执行时,就需要精巧的设计这些调用之间的因果关系,否则,程序的执行流程就会乱套了。

由于执行时间的不确定带来了极大的编程难度,rust语言本身增加了大量的辅助机制来帮助并不精通此道的程序员们使用这个特性。

Rust Future

Rust的Future总是用Result来表达,也就是说,需要显式指定返回值类型以及错误类型。

我们先定义一个普通函数,然后把它转换为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,这样的标记允许我们返回一个Future。
2 返回参数列表为<Item = u32, Error = Box<Error>>,精确描述了返回值与错误类型。

两个函数的返回值是不一样的,在普通函数中,用Ok(100)返回,这种写法使用到了Ok枚举类型,而在Future函数中,使用的是小写的ok方法。

函数改造完成后,要怎样执行呢?标准函数被调用后直接执行,需要注意的是,函数返回值是一个Result,必须要用unwarp取其内部包裹的值。

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

由于Future调用在实际的执行之前返回,更准确的说,返回的是一段在将来执行的代码。为了能够在将来执行,需要额外引入一种途径,我们通过调用reactor的run方法来执行Future。

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

注意返回值同样需要unwrap。

这样看起来,Future并没有想象中难,我们继续。

级联

级联是Future最重要的特性之一。考虑一个很典型的场景:假设你向你的父母发email,通知他们一起吃晚饭,收到确认后,你会开始准备晚餐,或者直接被拒绝。这些事件之间是有依赖关系的的,只有当前一个事件发生了,后一个事件才会启动。这一系列有因果关系的事件就是级联的概念,我们来看一个例子。

首先定义一个普通函数与一个Future。

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

但是对于Future而言,可以有更好的办法。由于Future是trait,有一个and_then内建方法,使用这个方法我们可以实现相同的语义,但是不用显式的创建以及调用两次reactor run。

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

看第一行,我们创建了一个Future,叫做chained_future,同时包含了my_fut与my_fn_squared。

这里有一个抖机灵的点,就是如何在不同的Future之间传递结果,rust通过闭包捕获环境变量的方式传递Future返回的结果。

执行过程如下:
1 调度并且执行my_fut()
2 当my_fut()执行完成,创建一个叫做retval的变量,并将my_fut()返回的结果保存至retval
3 将retval作为my_fn_squared(i: u32)的参数传递进去,调度并执行
4 把上述操作过程打包成一个名为chained_future的调用链,chained_future也是一个Future。

第二行与上文类似,调用了reactor.run来执行Future并返回结果。

通过这样的方式可以把无限个Future打包成一个调用链,这样的打包方式不会造成额外的开销,所以不需要担心性能问题。

rust的 borrow checked特性可能会Future chain写起来不是那么轻松,这种情况下可以尝试在闭包参数中添加move指令。

Future与普通函数混用

除了可以将Future级联之外,还可以将普通函数级联至Future。这个特性很有用,因为并不是所有函数都需要使用到Future特性。另一方面,也会出现在Future中调用外部函数的情况。如果这个函数返回类型不是Result,可以将这个函数简单的添加在一个闭包中。例如,假如有一个普通函数:

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

级联后的Future如下所示:

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,这个错误有点让人困惑。我们期望通过传递一个impl Future,但是我们用Result代替了。我们将会在第二部分讨论它。

范型

最后但并非最不重要的,Future与范型一起工作不需要借助于任何黑魔法。

看一个例子:

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(原因是因为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!()宏。

原文链接

https://dev.to/mindflavor/rust-futures-an-uneducated-short-and-hopefully-not-boring-tutorial---part-1-3k3

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容