深入浅出Rust Future - Part 1
本文译自Rust futures: an uneducated, short and hopefully not boring tutorial - Part 1,时间:2018-12-02,译者:
motecshine, 简介:motecshine
欢迎向Rust中文社区投稿,投稿地址,好文将在以下地方直接展示
- Rust中文社区首页
- Rust中文社区Rust文章栏目
- 知乎专栏Rust语言
- sf.gg专栏Rust语言
Intro
如果你是一个程序员并且也喜欢Rust这门语言, 那么你应该经常在社区听到讨论Future
这个库的声音, 一些很优秀的Rust Crates
都使用了Future
所以我们也应该对它有足够的了解并且使用它. 但是大多数程序员很难理解Future
到底是怎么工作的, 当然有官方 Crichton's tutorial
这样的教程, 虽然很完善, 但我还是很难理解并把它付诸实践.
我猜测我并不是唯一一个遇到这样问题的程序员, 所以我将分享我自己的最佳实践, 希望这能帮助你理解这个话题.
Futures in a nutshell
Future
是一个不会立即执行的特殊functions
. 他会在将来执行(这也是他被命名为future
的原因).我们有很多理由让future functions
来替代std functions
,例如: 优雅
,性能
,可组合性
.future
的缺点也很明显: 很难用代码去实现. 当你不知道何时会执行某个函数时, 你又怎么能理解他们之间的因果关系呢?
处于这个原因, Rust会试图帮助我们这些菜鸟程序员去理解和使用future
这个特性。
Rust's futures
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
. 看起来真的很简单。
Chaining
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
你的参数变量.
Mixing futures and plain functions
你也可以使用普通的函数来做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
,这个错误有点让人困惑. 我们将会在第二部分讨论它。
Generics
最后但并非最不重要的, 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!()宏。
深入浅出Rust Future - Part 1相关推荐
- rust future async/await
本文目录 hello world executor async generator await future futures库是很多人学习rust异步编程的第一站,今天我将通过一个简单的hello w ...
- rust python扩展_Rust语言优化Python性能案例
原标题:Rust语言优化Python性能案例 导读:Python 被很多互联网系统广泛使用,但在另外一方面,它也存在一些性能问题,不过 Sentry 工程师分享的在关键模块上用另外一门语言 Rust ...
- GO、Rust 这些新一代高并发编程语言为何都极其讨厌共享内存?
作者 | 马超 责编 | 王晓曼 出品 | CSDN博客 今天我想再来讨论一下高并发的问题,我们看到最近以Rust.Go为代表的云原生.Serverless时代的语言,在设计高并发编程模式时往往都 ...
- 使用 Rust 构建分布式 Key-Value Store
作者:唐刘 引子 构建一个分布式 Key-Value Store 并不是一件容易的事情,我们需要考虑很多的问题,首先就是我们的系统到底需要提供什么样的功能,譬如: 一致性:我们是否需要保证整个系统的线 ...
- Rust:Structuring and handling errors in 2020学习笔记
Rust: Structuring and handling errors in 2020- 学习笔记 直接上菜, 首先介绍两个新的Crate用于Rust Error处理,anyhow 和thiser ...
- 在 RustCon Asia 开启之前,聊聊 Rust 中国社区那些事
2019独角兽企业重金招聘Python工程师标准>>> 亚洲首届 RustCon Asia 将在 4 月 20 日于北京开启(也就是下周六啦~),大会为期 4 天,包括 20 日全天 ...
- Rust 语言新人入门指南
首先,学习 Rust 不能急躁.如果你抱着之前 1 天上手 Python, 2 天入门 Go 的经验和优越感来学习 Rust 的话,你可能会遭遇严重的失败感.如果你来自 Haskell/Ocaml 等 ...
- Rust:Rust语言介绍
Rust语言介绍 相关资源 Rust官网:https://www.rust-lang.org/ Rust编译器的源码:https://github.com/rust-lang/rust 语言设计和相关 ...
- corutine rust_Rust学习笔记#5:函数和trait
函数 基本语法 Rust的函数使用fn关键字开头,函数可以有一系列的输入参数,还有一个返回类型.函数返回可以使用return语句,可以使用表达式.下面是一个标准函数的示例,add函数接受两个i32的参 ...
最新文章
- Golang websocket
- iptables防***自动黑白名单脚本
- abap 判断当前用户是否有某事物码权限
- socket使用多进程实现并发的服务器
- 一个菜鸟从高一到大二的作品整理
- windows使用WSL安装linux子系统
- Oracle获取一年中的所有日期和一个月中的所有日期
- 深度学习2.0-13.神经网络与全连接层之张量实战
- element-ui组件dialog遇到form
- su灯光插件_V-Ray for SketchUp渲染外部照明快速入门
- 约瑟夫问题 c语言数组,约瑟夫问题的数组实现
- 服务器安装系统关闭磁盘阵列,戴尔服务器H330阵列卡取消磁盘阵列教程
- 小程序生成图片保存到系统相册_iSee图片专家下载|iSee图片专家 3.930 官方版
- 关系抽取调研-工业界
- 软件测试:什么样的公司需要专职测试?
- 项目难做,程序员难当,软件开发中的 9 大难题
- C++ 入门导引(这是一篇由GPT4写的文章)
- 手机通讯原理的工作原理
- 笔试——2019方正FPGA
- ubuntu 中w指令中的IDLE是什么意思
热门文章
- C和C++安全编码笔记:动态内存管理
- FFmpeg通过摄像头实现对视频流进行解码并显示测试代码(新接口)
- 循环神经网络(RNN)简介
- C++11中头文件atomic的使用
- OpenCV中SVM的使用
- 【Qt】qss样式表之:QCalendarWidget,日历窗口样式表设置
- 【Go】Go基础(八):结构体和方法
- linux隐藏文件的方法,Linux下隐藏文件的操作方法
- java多线程工具类_Java多线程系列之:线程的并发工具类
- java 跨年 周计算公式_如何跨年计算 两日期之间相隔的周数 with java8 time API