为什么 GAT、生命周期和异步的这种组合需要 `T: 'static`?

kmd*_*eko 6 lifetime rust async-await generic-associated-types

这段奇怪的代码会发出一个错误,do_the_thing()说它的T寿命不够长:

use std::future::Future;

trait Connection: Send {
    type ExecFut<'a>: Future<Output = ()> + Send
    where
        Self: 'a;

    fn transaction<F>(&mut self, _f: F)
    where
        F: for<'a> FnOnce(&'a mut Self) -> Box<dyn Future<Output = ()> + Send + 'a> + Send,
    {
        unimplemented!()
    }

    fn execute<'a>(&'a mut self) -> Self::ExecFut<'a> {
        unimplemented!()
    }
}

fn do_the_thing<T: Connection>(connection: &mut T) {
    connection.transaction(|conn| {
        Box::new(async move {
            conn.execute().await;
        })
    });
}
Run Code Online (Sandbox Code Playgroud)
use std::future::Future;

trait Connection: Send {
    type ExecFut<'a>: Future<Output = ()> + Send
    where
        Self: 'a;

    fn transaction<F>(&mut self, _f: F)
    where
        F: for<'a> FnOnce(&'a mut Self) -> Box<dyn Future<Output = ()> + Send + 'a> + Send,
    {
        unimplemented!()
    }

    fn execute<'a>(&'a mut self) -> Self::ExecFut<'a> {
        unimplemented!()
    }
}

fn do_the_thing<T: Connection>(connection: &mut T) {
    connection.transaction(|conn| {
        Box::new(async move {
            conn.execute().await;
        })
    });
}
Run Code Online (Sandbox Code Playgroud)

在操场上看到它。

据我所知,不应该'static需要任何隐含的界限。几乎所有事物都受到限制,并且无论如何,'a任何生命周期都应该比它更长寿。T尽管显然我可能缺少一些东西。

我还发现了一些没有帮助的“解决方案”:

  1. 按照编译器的建议添加'static是不合理的。它可以解决,但我想使用Connection具有非生命周期的 real 。'static
  2. 由于某种原因,删除Send上的绑定dyn Future使其通过编译。这对我来说毫无意义。
  3. 如果我使该函数成为非通用函数,但使用Connection不是'static它也可以编译,这似乎与编译器错误相矛盾。

以上并非真实代码;最初的动机源于使用AsyncConnectiondiesel -async crate和类似的代码结构。然而,我希望它能够代表核心问题,并且通过理解这里的问题和潜在的解决方案,可以对其进行调整。

kmd*_*eko 5

通过进一步的修补和研究,我发现了这个问题 - GATs: Decide if to have defaults forwhere Self: 'a - 关于 的where条款type ExecFut<'a>。从该问题的评论来看,似乎其他人在执行此限制时也遇到了类似的错误。

该问题提供了一种解决方法,即将您的特征分为两部分 - 一个用于关联类型,另一个用于方法,以便where Self: 'a可以省略该子句:

这破坏了我的代码。解决方法?

首先,如果任何代码无法添加所需的边界,我们确实需要反馈。其次,解决方法是将 GAT 转变为超级特征。使用上面的示例,我们的新代码将如下所示:

trait IterableSuper {
    type Item<'x>;
}
trait Iterable: IterableSuper {
    fn iter<'a>(&'a self) -> Self::Item<'a>;
}
Run Code Online (Sandbox Code Playgroud)

在我的示例中执行此操作可以使其编译,但在我的目标用例中它确实开始变得混乱。

我见过提到的另一个解决方法是引入一个虚拟函数,该函数使用关联的类型但不与self. 这也允许您省略该where Self: 'a子句,因此代码可以编译:

trait Connection: Send {
    type ExecFut<'a>: Future<Output = ()> + Send;
 // ^^^^^^^^^^^^^^^^ does not need `where Self: 'a`

    fn transaction<F>(&mut self, _f: F)
    where
        F: for<'a> FnOnce(&'a mut Self) -> Box<dyn Future<Output = ()> + Send + 'a> + Send,
    {
        unimplemented!()
    }

    fn execute<'a>(&'a mut self) -> Self::ExecFut<'a> {
        unimplemented!()
    }
    
    fn _disable_lint_for_exec_fut<'a>(_: Self::ExecFut<'a>) { }
 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dummy function
}
Run Code Online (Sandbox Code Playgroud)

作为旁注,还有另一个链接的问题 - GAT、异步和发送边界的问题- 描述了如上所述的边界之间的奇怪交互,Send这可以解释为什么删除Send边界会导致代码片段编译。还是很奇怪。