首页 > 解决方案 > 在需要特征对象的上下文中使用借用参数的异步函数

问题描述

我最近开始在 Rust 中使用异步流,并且一直发现自己处于想要在Stream. 异步函数通常来自我无法控制的库,但为了举例,假设它们看起来像这样:

async fn bar_str(s: &str) -> String {
    s.to_string()
}

async fn bar_string(s: String) -> String {
    s
}

另外为了简单起见,假设我只是想使用这些函数来实现如下特征(不涉及实际的流内容):

use std::future::Future;

trait Foo {
    fn bar(self) -> Box<dyn Future<Output = String>>;
}

对于这种String情况,这就像您期望的那样:

impl Foo for String {
    fn bar(self) -> Box<dyn Future<Output = String>> {
        Box::new(bar_string(self))
    }
}

对于异步函数借用的情况,它不会。

impl Foo for &str {
    fn bar(self) -> Box<Future<Output = String>> {
        Box::new(bar_str(self))
    }
}

这无法编译:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter '_ in function call due to conflicting requirements
  --> foo.rs:23:18
   |
23 |         Box::new(bar_str(self))
   |                  ^^^^^^^^^^^^^
   |
...

我可以理解为什么这是一个问题,并且我理解async fn语法为这样的借用参数提供了特殊处理(尽管我对它的实际检查、脱糖等一无所知)。

我的问题是关于在这些情况下最好的做法是什么。有什么方法可以重现async fn我的非async fn代码中的魔力吗?我是否应该避免借用异步函数(如果可以,因为这通常是我没有做出的决定)?在我目前正在编写的代码中,如果它们使阅读和编写这种事情变得更好,我很乐意使用实验性或非必要的面向未来的解决方案。

标签: asynchronousrust

解决方案


我认为问题不在于事情,async而在于Box<dyn Trait>事情。事实上,它可以通过一个简单的特征来复制:

use std::fmt::Debug;

trait Foo {
    fn foo(self) -> Box<dyn Debug>;
}

impl Foo for String {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self)
    }
}

impl Foo for &str {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self) // <--- Error here (line 15)
    }
}

完整的错误信息是:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/lib.rs:15:18
   |
15 |         Box::new(self)
   |                  ^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the impl at 13:14...
  --> src/lib.rs:13:14
   |
13 | impl Foo for &str {
   |              ^
note: ...so that the expression is assignable
  --> src/lib.rs:15:18
   |
15 |         Box::new(self)
   |                  ^^^^
   = note: expected `&str`
              found `&str`
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the expression is assignable
  --> src/lib.rs:15:9
   |
15 |         Box::new(self)
   |         ^^^^^^^^^^^^^^
   = note: expected `std::boxed::Box<(dyn std::fmt::Debug + 'static)>`
              found `std::boxed::Box<dyn std::fmt::Debug>`

关于最后两行发生的事情有一个很好的暗示......这是什么Box<(dyn Debug + 'static)>东西?

当您编写dyn Trait时,实际上对实现该特征的类型有一个隐式约束,因此这两个是同一件事: 'static

Box<dyn Debug>
Box<(dyn Debug + 'static)>

但这意味着我们只能对类型为 的值进行装箱'static。而且&'a str不是静态类型,所以不能这样装箱。

像往常一样,简单的解决方案是克隆,如果可能的话。这编译并且它不是太难看:

impl Foo for &str {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self.to_owned())
    }
}

或者,如果您只使用静态字符串,那么&'static str实际上是静态的,您可以编写:

impl Foo for &'static str {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self)
    }
}

如果您确实想要或需要借用,那么装箱的 dyn 对象必须在某个生命周期内是通用的。您必须更改特征的返回类型,如下所示:

use std::fmt::Debug;

trait Foo<'a> {
    fn foo(self) -> Box<dyn Debug + 'a>;
}

impl Foo<'static> for String {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self)
    }
}

impl<'a> Foo<'a> for &'a str {
    fn foo(self) -> Box<dyn Debug + 'a> {
        Box::new(self)
    }
}

但现在要注意,Box<dyn Debug + 'a>它本身不是静态类型,而是类型本身的生命周期为'a.


推荐阅读