Async 工作组兴奋地宣布,Lang 团队最近批准了RFC 3668“Async 闭包”。在这篇文章中,我们将简要说明 async 闭包存在的原因,解释它们当前的缺点,最重要的是,宣布一项征集测试的行动,希望大家在 Nightly Rust 上测试它们。
背景故事
Async 闭包最初在引入 async
/await
的RFC 2394中提出。自 async-await 实现后不久,Nightly 版本中就存在了对 async 闭包的简单处理不久之后,但直到最近,async 闭包才简单地解糖为返回 async 块的闭包
let x = async || ;
// ...was just sugar for:
let x = ;
这有一个根本性的限制,即无法表达一个返回借用捕获状态的 Future 的闭包。
与此 somewhat 相关的是,在被调用者端,当用户希望将 async 闭包作为参数时,他们通常将其表达为两种不同泛型类型的约束
这也导致了一个额外的限制,即在不进行装箱的情况下,无法表达使用这种方式的高阶 async fn 约束(因为对 F
的高阶 trait 约束不能导致 Fut
的高阶类型),从而导致不必要的分配
async_callback;
这些限制在 Niko 关于 async 闭包和借用的博客文章,以及后来 compiler-errors 关于async 闭包为何如此的博客文章中得到了详细阐述。
RFC 3668 有什么帮助?
好的,那么最近的工作专注于重新实现 async 闭包使其能够进行借用,并设计了一套 async fn trait。尽管 async 闭包作为语法已经存在,但这项工作引入了一个新的 async fn trait 系列,async 闭包(以及所有其他返回 Future 的可调用类型)实现了这些 trait。它们可以写成这样
(目前,如何精确地表达这种约束是一个开放问题,因此这两种语法是并行实现的。)
RFC 3668 详细阐述了这项实现工作的原因,确认我们需要一流的 async 闭包和 async fn trait,它们允许我们表达 async 闭包的*借用*能力——如果您对整个故事感兴趣,请阅读该 RFC!
那么我该如何提供帮助?
我们非常希望您测试这些新功能!首先,在一个最近更新的 Nightly 编译器上,启用 #![feature(async_closure)]
(请注意,出于历史原因,此特性名称没有复数)。
Async 闭包被设计为可以(几乎在所有情况下)与返回 async 块的闭包进行直接替换
// Instead of writing:
takes_async_callback;
// Write this:
takes_async_callback;
在被调用者端,编写 async fn trait 约束,而不是编写返回 Future 的“常规”fn trait 约束
// Instead of writing:
// Write this:
// Or this:
或者如果您正在使用装箱模拟高阶 async 闭包
// Instead of writing:
// Write this:
// Or this:
与 async 生态系统交互的缺点
如果您打算尝试重写您的 async 项目,有几个缺点需要注意。
您不能直接命名输出 Future
当您使用*旧*样式(在有一流 async fn trait 约束之前)命名 async 可调用约束时,由于需要使用两个类型参数,您可以对约束的 Future
部分添加额外的约束(例如 + Send
或 + 'static'
),像这样
目前没有办法对调用 async 闭包返回的 Future 添加类似的约束,因此如果您需要这样约束您的回调 Future,那么您还不能使用 async 闭包。
我们期望在中长期通过返回类型表示法语法来支持这一点。
闭包签名推断的细微差异
将 async 闭包传递给泛型 impl Fn(A, B) -> C
约束可能不会总是立即将闭包的参数推断为 A
和 B
,偶尔会导致奇怪的类型错误。有关此示例,请参见rust-lang/rust#127781
。
我们期望随着工作的进展改进 async 闭包签名推断。
fn()
指针
Async 闭包不能强制转换为 一些库将其回调接受为函数*指针*(fn()
)而不是泛型。Async 闭包目前不实现从闭包到 fn() -> ...
的相同强制转换。一些库可能会通过将其 API 调整为接受泛型 impl Fn()
而非 fn()
指针作为参数来缓解此问题。
除非有特别好的理由支持,否则我们不期望实现此强制转换,因为通常可以通过调用者手动使用内部函数项或使用 Fn
约束来处理,例如
// Or if you don't need to take *exactly* a function pointer,
// you can rewrite `needs_fn_pointer` like:
// Or with `AsyncFn`: