作者介绍
Nick Cameron,PingCAP 研发工程师,Rust core team 成员,专注于分布式系统、数据库领域和 Rust 语言的发展。
futures
库在这几年中变化很大。最新的版本为 0.3(crates.io 发布的 futures
预览版)。然而,有许多早期代码是 futures 0.1 系列版本,且一直没有更新。这样的分裂事出有因 - 0.1 和 0.3 版本之间变化太大。0.1 版本相对稳定,而 0.3 版本一直处于快速变化中。长远来看,0.3 版本最终会演进为 1.0。有一部分代码会进入 Rust 标准库,其中的第一部分已在最近发布了稳定版,也就是 Future
trait。机械性变化
Future
签名中包含了一个 Error
关联类型,而且 poll
总是会返回一个 Result
。0.3 版本里该错误类型已被移除,对于错误需要显式处理。为了保持行为上的一致性,我们需要将代码里所有 Future<Item=Foo, Error=Bar>
替换为 Future<Output=Result<Foo, Bar>>
(留意 Item
到 Output
的名称变化)。替换后, poll
就可以返回和以前一样的类型,这样在使用 futures 的时候无需任何变化。TryFuture
类型,基本上可以看作 Future<Output=Result<...>>
的替代。使用这个类型,意味着你需要在 Future
与 TryFuture
之间转换,因此最好还是尽量避免吧。TryFuture
类型包含了一个 blanket implementation,这使它可以通过 TryFutureEx
trait 轻松将某些函数应用于此类 futures。Future::poll
方法会接受一个新的上下文参数。这基本上只需要调用 poll
方法即可完成传递(偶尔也会忽略)。Compat01As03
)。我们在调用依赖关系时会用到这些。wait
方法已被从 Future
trait 中移除。这是让人拍手称快的变化,因为该方法确实够反人性,而且本身可以用 .await
或 executor::block_on
代替(需要注意的是后者可能会阻断整个进程,而并不只是当前执行的 future)。Pin
Pin
是一个频繁使用的类型, Future::poll
方法签名的 self
类型对其尤为青睐。除了对这些签名进行一些机械性的处理之外,我还得借助于 Pin::get_unchecked_mut
与 Pin::new_unchecked
这两种方法(均为不安全方法)对 futures 的项目字段做一些变更。Pin
作为一个类型构造,只有用于指针类型(如 Pin<Box<_>>
)时才会生效。NonNull
),并不是指针类型。mem::swap
时会发生移动)。需要注意的移动只能发生在指针被定位之前,而非之后。Unpin
trait,这意味着无论此类型移动与否都不会有任何影响。换句话说,即使指向该类型的指针没有被定位,我们也可以放心把它当作被定位的。Pin
与 Unpin
并没有置入 Rust 语言,虽然某些特性会对指针定位有间接依赖。指针定位由编译器强制执行,但编译器本身却不自知(这点非常酷,也体现了 Rust 特性系统对此类处理的强大之处)。它是这样工作的:Pin<P<T>>
只允许对于 P
的安全访问,禁止移动 P
指向的任何数值,除非 T
应用了 Unpin
(代码编写者已宣称 T
并不在意是否被移动)。任何允许删除没有执行 Unpin
数值的操作(可变访问)都是 unsafe
的,且应该由程序编写者决定是否要移动任何数值,并保证之后的安全代码中不可删除任何数值。Pin
使用了不安全的方法,你就需要上面的要点,以保证指针定位的稳定。 std::pin docs 提供了更多的解释。我在许多地方通过字段投射的方式为另外一个 future 调用 poll
方法(有时是间接的)。为了达到这个目的,需要一个已定位的指针,这也意味着能你需要结构性指针定位。如,你可以将 Pin<&mut T>
字段投射至 Pin<&mut FieldType>
。函数
Async
变成了 Poll
,Ok
变成了 ready
,for_each
变成 then
,then
变成 map
,Either::A
变成 Either::Left
。T
类型生成数值的 future,而不会直接返回数值本身。Future
trait 移至扩展 crate 里。这个问题本身不难修复,只是有时候不容易从错误信息中判定。LoopFn
LoopFn
这个 future 构造,用于处理多次执行某动作的 futures。LoopFn
在 0.3 版本中被移除,这样做的原因个人认为可能是 for
循环本身是 async
的函数,或者 streams 才是长远看来的更佳解决方案。为了让我们的迁移过程简单化,我为 Futures 0.3 写了我们自己版本的 LoopFn
future,其实大部分也都是复制粘贴的工作,加上一些调整(如处理指针定位投射):code。后来我将几处 LoopFn
用法转换为 streams,对代码似乎有一定改进。Sink::send_all
Sink::send_all
结构变了。0.1 版本里,Sink::send_all
会获取 stream 的所有权,并在确定所有 future 都完成后返回 sink 以及 stream。0.3 版本里, Sink::send_all
会接受一个对 stream 的可变引用,不返回任何值。我自己写了一个 兼容层 在 futures 0.3 里模拟 0.1 版本的 sink。这不是很难,但也许有更好的方式来做这件事。💡 文中划线部分均有跳转,点击【阅读原文】查看英文原版文章。