【问题标题】:Using async functions that borrow arguments in contexts where trait object is needed在需要特征对象的上下文中使用借用参数的异步函数
【发布时间】:2026-01-19 07:00:02
【问题描述】:

我最近开始在 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 代码中所做的魔术吗?我是否应该避免借用异步函数(如果可以,因为这通常是我没有做出的决定)?在我目前正在编写的代码中,我很乐意使用实验性或非必要的面向未来的解决方案,如果它们使阅读和编写这种事情变得更好的话。

【问题讨论】:

    标签: asynchronous rust


    【解决方案1】:

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

    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&lt;(dyn Debug + 'static)&gt; 是什么东西?

    当你写 dyn Trait 时,实际上有一个 implicit 'static 约束到实现该特征的类型,所以这两个是同一件事:

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

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

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

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

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

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

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

    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&lt;dyn Debug + 'a&gt; 本身并不是静态类型,但类型本身的生命周期为 'a

    【讨论】:

    • 谢谢,这很有帮助,但恐怕我可能已经过度最小化了,这进一步减少了一点。例如,我无法克隆,因为我需要调用需要引用的函数(我无法控制)。我还尝试添加一个显式的生命周期参数并遇到了问题,但我会仔细研究如何使其工作。
    • @TravisBrown:是的,async 问题通常是多个问题叠加在一起。无论如何,您的原始代码具有显式生命周期does work。也许您可以发布一个显示新问题的游乐场链接?
    • 刚刚确认线程通过显式生命周期确实有效(至少在我在实际代码中遇到这种情况的地方之一)!我一定是太快放弃了。感谢您的澄清。