返回类型记法 MVP:呼吁测试!

2024 年 9 月 26 日 · Michael Goulet 代表 Async 工作组

Async 工作组很高兴地宣布 RFC 3654 返回类型记法 (RTN) 已准备好在 nightly Rust 上进行测试。在本文中,我们将简要描述该功能。

背景故事

Rust 1.75 稳定了 trait 中的 async fn (AFIT) 和 trait 中返回位置的 impl Trait (RPITIT)。这些会被解糖为匿名的泛型关联类型 (GATs)。然而,与 GATs 不同的是,这些类型的用户不能使用 where 子句来进一步限制这些返回类型。这被称为 “Send bound” 问题,因为它经常影响 async 生态系统中 future 的 Send bound。

示例

考虑一个 trait Foo,其中有一个 method 方法返回类型为 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());
}

然而,使用 GATs 意味着 Foo 的实现者必须明确写出返回类型,type MethodFuture = ...,而当返回类型是匿名的、无法命名的 Future 类型时,这()不起作用!

解决方案

RFC 3654 中,我们引入了返回类型记法 (RTN)。这将允许我们编写 where 子句 bound 来限制使用 trait 中的 async fn (AFIT) 和 trait 中返回位置的 impl Trait (RPITIT) 的函数和方法的返回类型。扩展上面的例子,RTN 允许我们编写

fn needs_sendable_future<T: Foo>()
where
    T::method(..): Send + 'static // Yay!
{
    spawn(T::method());
    //~^ Works!
}

限制

目前,RTN 仅适用于使用以下特征的 trait 关联函数和方法(具有生命周期泛型,而非 const 或类型泛型):

  • trait 中的 async fn (AFIT),或
  • trait 中返回位置的 impl Trait (RPITIT),其中 impl Trait 是最外层的返回类型,即 -> impl Trait,而不是 -> Box<impl Trait>

这些限制在 RFC 3654 中有更详细的描述。

我如何提供帮助?

我们非常希望您能在最新的 Rust nightly 编译器上测试此功能1

具体来说,我们希望您能找出那些您不必要地使用 + Send 或类似 bound 限制了 trait 定义的 trait

// 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,
{}

同样,我们希望您能找出目前出于相同原因而返回 GATs 的 trait

// 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-bound 繁重的 async Rust 生态系统中使用 trait 中的 async fn (AFIT) 变得更容易。

一如既往,请查阅 RFC 本身,了解我们确定此设计的详细解释,特别是常见问题解答和基本原理