Rust 异步借用生命周期

Rust Async Borrow Lifetimes

我正在尝试制作一个允许异步链接副作用的帮助程序,但我无法获得正确的通用边界,以便编译器理解未来的输出比用于构建它的引用更有效。

Playground Link

它的要点归结为:

struct Chain<T> {
    data: T
}
impl<T> Chain<T> {
    pub async fn chain<E, Fut, F>(self, effect: F) -> Result<T, E>
        where
            Fut: Future<Output=Result<(), E>>,
            F: FnOnce(&T) -> Fut
    {
        todo!()
    }
}

给出编译器错误

error: lifetime may not live long enough
  --> src/main.rs:39:32
   |
39 |     let r = chain.chain(|this| this.good("bar")).await;
   |                          ----- ^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |                          |   |
   |                          |   return type of closure `impl Future` contains a lifetime `'2`
   |                          has type `&'1 MyData`

如果我们修复 chain 以便它可以推断出引用在与未来相同的生命周期内可用:

impl<T> Chain<T> {
    pub async fn chain<'a, E, Fut, F>(self, effect: F) -> Result<T, E>
        where
            T: 'a, 
            Fut: 'a + Future<Output=Result<(), E>>,
            F: FnOnce(&'a T) -> Fut
    {
        effect(&self.data).await?;
        Ok(self.data)
    }
}

我们收到一个关于移动 self.data 的新编译器错误,而它仍然被借用。

error[E0505]: cannot move out of `self.data` because it is borrowed
  --> src/main.rs:30:12
   |
23 |     pub async fn chain<'a, E, Fut, F>(self, effect: F) -> Result<T, E>
   |                        -- lifetime `'a` defined here
...
29 |         effect(&self.data).await?;
   |         ------------------
   |         |      |
   |         |      borrow of `self.data` occurs here
   |         argument requires that `self.data` is borrowed for `'a`
30 |         Ok(self.data)
   |            ^^^^^^^^^ move out of `self.data` occurs here

我想 |this| futures::future::ready(Err(this)) 中存在病态关闭,这会导致早期 return 借用仍然“有效”。

问题

我们如何让 chain 工作?我通常使用的块作用域技巧似乎无济于事。是否可以添加一组 where 约束来证明借用和最终移动的生命周期不相交?

您似乎正在尝试实施 future.then()

如果您知道这一点并且将其作为练习进行,您可能应该以 效果 方法 return 值的方式设计它,并将这些值用于 return 来自 chain 方法。这样你就可以强制执行正确的操作顺序。据我了解您的设计,在 chain 方法中等待 effect 不会给您带来好处,因为您的 skip 函数也是异步的,并且会 return 未来(return 实际 chain 方法的类型是 Future >,因为 async 以这种方式工作:它会在将来包装您的显式 return 类型)。

因此,在 中等待 effect 是没有意义的,您仍然需要 await 每当你使用它时——在你真正在 之外等待它之前,什么都不会发生——期货就是这样懒惰的。

TL;DR 我会将您的 effect 方法安排为 return 值,并将 chain 安排为 return 这些值

在这种特殊情况下,当前的约束语法和缺乏更高级的类型无法让您表达您想要的内容。

您可以使用 higher-rank trait boundfor<'a> 语法,在 where 子句中引入一个中间通用生命周期参数 'a 来指示约束必须是任何 生命周期有效。这在这里是必要的,你的第一个修复不起作用的原因是因为 'a 作为 chain 上的通用意味着生命周期由调用者 确定 ,但是,self 的生命周期在构造上比调用者可以选择的任何生命周期都短。所以稍微更正确的语法(并且与脱糖的原始代码相同)是:

pub async fn chain<E, Fut, F>(self, effect: F) -> Result<T, E>
    where
        Fut: Future<Output = Result<(), E>>,
        F: for<'a> FnOnce(&'a T) -> Fut
{
    ...

但这根本没有帮助,因为 Fut'a 之间仍然没有关联。遗憾的是,无法跨多个约束使用相同的 for<'a>。您可以尝试使用 impl Trait 一次定义它,但不支持:

pub async fn chain<E, F>(self, effect: F) -> Result<T, E>
    where F: for<'a> FnOnce(&'a T) -> (impl Future<Output = Result<(), E>> + 'a)
{
    ...
error[E0562]: `impl Trait` not allowed outside of function and method return types
  --> src/lib.rs:35:44
   |
35 |         where F: for<'a> FnOnce(&'a T) -> (impl Future<Output = Result<(), E>> + 'a)
   |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

希望将来能更好地支持更高种类的类型。这种特殊情况 可能 通过使用 almost-complete generic associated types 功能在每晚都有解决方案,但我还没有找到它.

所以唯一真正的解决方法是使用命名类型作为 return 值,这实际上只给我们留下了特征对象:

use std::pin::Pin;
use futures::future::FutureExt;

pub async fn chain<E, F>(self, effect: F) -> Result<T, E>
    where F: for<'a> FnOnce(&'a T) -> Pin<Box<dyn Future<Output = Result<(), E>> + 'a>>
{
    ...

let r = chain.chain(|this| this.good("bar").boxed()).await;

附带说明一下,您的 bad 案例仍然无法编译并且确实无法工作,因为您将 return 引用本地值。