函数指针的生命周期应该是for<'r>,而不是for<'a, '_>。

4
有时我会遇到生命周期的问题。我还在学习中,不知道这里发生了什么:
use std::future::Future;
use futures::future::{BoxFuture, FutureExt};

struct M{}
struct Client{}

impl Client {
    async fn send_and_expect<'a>(
        &'a mut self,
        m: &M
    ) -> std::result::Result<(), ()> {
        Ok(())
    }
    
    pub fn connection_retrier<'a, T>(
        f: fn(&'a mut Self, &M) -> T,
        f_self: &'a mut Self,
        f_m: &'a M,
    )-> BoxFuture<'a, std::result::Result<(),()>>
    where
        T: Future<Output = std::result::Result<(), ()>> + 'a
    {
        async move {
            Client::send_and_expect(f_self, f_m).await
        }.boxed()
    }
    
    async fn send_with_retry<'a>(&'a mut self) -> std::result::Result<(), ()> {
        let m = M{};
        Client::connection_retrier(
                    Client::send_and_expect, 
                    &mut *self, &m).await
    }
}

错误:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/lib.rs:31:21
   |
11 |     ) -> std::result::Result<(), ()> {
   |          --------------------------- the `Output` of this `async fn`'s found opaque type
...
31 |                     Client::send_and_expect, 
   |                     ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'r> fn(&mut Client, &'r M) -> impl futures::Future`
              found fn pointer `for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future

Playground

对于 for<'r> fn(&mut Client, &'r M) -> impl futures::Future,为什么&mut Client没有生命周期,我感到非常困惑。在 for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future 中,下划线_的含义是什么?

我非常想了解这里发生了什么。

1个回答

5

清理代码

为了从编译器的角度来看待这个代码,让我们将该示例中的所有生命周期参数改为显式且具有不同的名称,扩展 async fn 语法,然后查看错误如何更改。

在生命周期推断和解糖之后,上述示例等效于以下示例。

// name the second lifetime, and expand the async fn sugar.
fn send_and_expect<'a, 'b>(&'a mut self, m: &'b M) -> impl Future<Item=Result<(), ()>> + 'a + 'b 
{ ... }

// rename 'a to 'c to avoid ambiguous lifetime names
pub fn connection_retrier<'c, T>(
        f: for<'d> fn(&'c mut Self, &'d M) -> T, // name the implicit higher-ranked lifetime here
        f_self: &'c mut Self,
        f_m: &'c M,
    )-> BoxFuture<'c, std::result::Result<(), ()>>
where T: Future<Output = std::result::Result<(), ()>> + 'c 
{ ... }

// rename 'a to 'e to avoid ambiguous lifetime names
async fn send_with_retry<'e>(&'e mut self) -> std::result::Result<(), ()> {

在进行此更改后,错误变为:

Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/lib.rs:31:21
   |
11 |     ) -> std::result::Result<(), ()> {
   |          --------------------------- the `Output` of this `async fn`'s found opaque type
...
31 |                     Client::send_and_expect, 
   |                     ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'d> fn(&mut Client, &'d M) -> impl futures::Future`
              found fn pointer `for<'a, 'b> fn(&'a mut Client, &'b M) -> impl futures::Future`

这应该可以澄清关于'_的问题:它只是编译器给send_and_expect推断出的第二个生命周期参数的名称。至于&mut Client上缺少的生命周期,你可以看到这里仍然缺失。由于某些我不完全理解的原因,并且以依赖于给定的确切错误消息的方式,编译器有时会在打印引用类型时省略具体生命周期,但毫无疑问,该引用的生命周期是'c
解决错误:
转到实际问题。函数f的签名表明,connection_retrier希望一个函数(1)以生命周期&'c的引用为参数,并且(2)以任何其他生命周期的引用为参数,(3)并返回一个Future类型,其有效期与'c一样长,正如您的where定义所指定的那样。
当我们将send_and_expect传递给connection_retrier时,为了使签名匹配,编译器将其强制转换为for<'d> send_and_expect::<'c, 'd>类型。但是该类型不符合上述条件(3)!与常规函数不同,async函数的默认行为是捕获其返回类型中的所有输入生命周期,因此for<'d> send_and_expect::<'c, 'd>类型的返回类型实际上是impl Future<Item=Result<(), ()>> + 'c + 'd,您可以通过查看send_and_expect的展开签名来了解。
由于该类型从两个生命周期'c'd借用,并且没有约束条件'd: 'c(即:'d'c更长寿),如果'd先结束,则它可能不能在'c的整个生命周期内保持有效。正是这种不匹配导致您收到相当神秘的生命周期错误。根据您的首选语义,您有两种方法可以解决此问题:
  • Remove the higher-ranked bound from f entirely, and specify exactly the lifetimes you will be calling it with in connection_retrier (both &'c.)

    pub fn connection_retrier<'c, T>(
            f: fn(&'c mut Self, &'c M) -> T
            f_self: &'c mut Self,
            f_m: &'c M,
        ) -> BoxFuture<'c, std::result::Result<(), ()>>
    where T: Future<Output = std::result::Result<(), ()>> + 'c 
    { ... }
    
  • Keep the signature of connection_retrier the same and specify that the future returned by send_and_expect only borrows from its first argument. To do this, you will need to drop the async fn sugar on the signature and wrap the body in an async move block.

    fn send_and_expect<'a, 'b>(&'a mut self, m: &'b M) -> impl Future<Output=Result<(), ()>> + 'a 
    { async move { ... } }
    
请注意,您不能通过将f的类型编写为for<'d: 'c> fn(&'c mut Self, &'d M) -> T来解决此问题,因为目前不允许在普遍量化的生命周期中使用边界。

我想运行上述代码以更容易理解,但当我编译清理代码时,出现了 fn send_and_expect<'a, 'b>(&'a mut self, m: &'b M) -> impl Future<Item=Result<(), ()>> + 'a + 'bResult<(), _> 不是一个 future。也许我使用了错误的 futures 版本?futures = "0.3.14"。我不知道如何解决这个错误。你能帮我吗? - boundless-forest
感谢您的指导,使用 async move {} 包装 Ok(()) 是有效的。再次感谢。 - boundless-forest
@Gatonito 如果你只是想问为什么错误中的 for<'a, '_> 变成了 for<'a, 'b>,那是因为生命周期推断自动为 send_and_expect 的签名中第二个引用创建了一个生命周期参数。由于这个参数最初没有名称,编译器将其称为 '_ - AlphaModder
我选择了第一个解决方案,因为第二个无法使用,因为send_and_expect是递归的,所以它不能返回一个impl Future,它必须返回一个BoxFuture。然而,这给我带来了一个新的错误:https://stackoverflow.com/questions/67239248/recursive-async-function-that-borrows-mutable-variable-twice。你有什么想法吗? - Gatonito
我觉得我在 pub fn connection_retrier<'a, T>(f: fn(&'a mut Self, &'a Message) -> T, 的概念上卡住了。一开始我以为 fn 是一个泛型函数,其参数的生命周期是通用的,但现在我认为它是一个接收具有确切生命周期 'a 的参数的函数。然而,在我上面的新问题中,当我调用 f 时,我不明白为什么 f_self 需要借用 'a。它只需要在 f 需要它的时候进行借用即可。然后它可以再次被借用。 - Gatonito
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接