异步工作组很高兴地宣布,RFC 3654 返回类型标记(RTN)已准备好在 Rust nightly 版本上进行测试。在这篇文章中,我们将简要描述该功能。
背景
Rust 1.75 稳定了 traits 中的 async fn(AFIT)和 traits 中的返回位置 impl Trait(RPITIT)。这些会解糖为匿名泛型关联类型(GAT)。但是,与 GAT 不同,这些类型的用户不能使用 where
子句来进一步限制这些返回类型。这被称为 “send 边界” 问题,因为它通常会影响异步生态系统中 future 的 Send
边界。
一个例子
考虑一个具有 method
方法的 trait Foo
,该方法返回 impl Future<Output = ()>
类型。我们想要编写一个函数,该函数调用 method
并将 future 生成到另一个线程上。
fn spawn<T>(f: impl Future<Output = T> + Send + 'static) {}
trait Foo {
fn method() -> impl Future<Output = ()>; // <-- RPITIT.
}
fn needs_sendable_future<T: Foo>()
where
// How do we further restrict `T::method()`
// to be `Send + 'static`?
{
spawn(T::method());
//~^ ERROR: `impl Future<Output = ()>` is not `Send`!
}
具体来说,我们可能不想限制 Foo
的声明,因为在声明中更改它会限制 Foo
的所有实现。
trait Foo {
fn method() -> impl Future<Output = ()> + Send + 'static;
// ~~~~~~~~~~~~~~~~
// Not what we want.
}
因此,在稳定的 Rust 中,我们在使用 AFIT 或 RPITIT 时无法表达这种限制。相反,如果我们直接使用 GAT,我们今天可以表达这一点。
trait Foo {
type MethodFuture: Future<Output = ()>;
fn method() -> Self::MethodFuture;
}
fn needs_sendable_future<T: Foo>()
where
// We can restrict this to only implementors of `Foo`
// whose `MethodFuture` is `Send + 'static`, so we can
// call `spawn` below:
T::MethodFuture: Send + 'static
{
spawn(T::method());
}
但是,使用 GAT 意味着 Foo
的实现者必须显式写出返回类型,type MethodFuture = ...
,如果我们有一个匿名的、无法命名的 Future
类型,它(尚未)无法工作!
解决方案
在 RFC 3654 中,我们引入了返回类型标记(RTN)。这将允许我们编写 where
子句边界,这些边界限制了在 traits 中使用 async fn (AFIT) 和 traits 中的返回位置 impl Trait (RPITIT) 的函数和方法的返回类型。扩展上面的示例,RTN 允许我们编写
fn needs_sendable_future<T: Foo>()
where
T::method(..): Send + 'static // Yay!
{
spawn(T::method());
//~^ Works!
}
限制
目前,RTN 仅允许用于具有生命周期泛型(不是 const 或类型泛型)的 trait 关联函数和方法,这些函数和方法使用
- traits 中的 async fn (AFIT) 或
- traits 中的返回位置 impl Trait (RPITIT),其中 impl Trait 是最外层的返回类型,即
-> impl Trait
,而不是-> Box<impl Trait>
。
这些限制在 RFC 3654 中有更详细的描述。
我如何帮助?
我们希望您在最新的 Rust nightly 编译器上测试此功能1。
具体来说,我们希望您识别出那些不必要地使用 + Send
或类似边界限制您的 trait 定义的 traits。
// Instead of writing a trait like:
trait Foo {
fn method() -> impl Future<Output = ()> + Send + 'static;
}
// Write this:
trait Foo {
async fn method();
}
// And then at the call site, add:
fn use_foo<T: Foo>()
where
T::method(..): Send + 'static,
{}
类似地,我们希望您识别出当前出于相同原因返回 GAT 的 traits。
// Instead of writing this in the trait and call site:
trait Foo {
type MethodFuture: Future<Output = ()>;
fn method() -> Self::MethodFuture;
}
fn use_foo<T: Foo>()
where
T::MethodFuture: Send + 'static,
{}
// Write this:
trait Foo {
async fn method();
}
fn use_foo<T: Foo>()
where
T::method(..): Send + 'static,
{}
但是请注意,我们尚不支持类型位置的 RTN。因此,虽然使用第一个版本,您可以编写
struct Bar<T: Foo> {
field: T::MethodFuture,
}
但是,使用第二个版本,您还不能编写
struct Bar<T: Foo> {
field: T::method(..),
}
我们有兴趣了解您会在哪些地方遇到此限制。
我们很高兴 RTN 可以更轻松地在 Send
边界重的异步 Rust 生态系统中使用 traits 中的 async fn (AFIT)。
与往常一样,请查看 RFC 本身,以详细了解我们为什么选择这种设计,特别是 常见问题和理由。
-
请确保运行
rustup update nightly
(或您管理 Rust 版本的方式),因为该功能非常新并且仍然不稳定! ↩