【问题标题】:Rust Async Borrow LifetimesRust 异步借用生命周期
【发布时间】:2021-12-31 01:20:14
【问题描述】:

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

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)) 的病态关闭,这将导致借用仍然“活着”而提前返回。

问题

我们怎样才能让chain 工作?我正常的块作用域技巧似乎没有帮助。是否可以添加一组 where 约束来证明借用和最终移动的生命周期不相交?

【问题讨论】:

    标签: asynchronous rust borrow-checker generic-constraints


    【解决方案1】:

    这种特殊情况是当前的约束语法和缺乏更高种类的类型无法表达你想要的。

    您可以使用higher-rank trait boundfor&lt;'a&gt; 语法)在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&lt;'a&gt;。您可以尝试使用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)
       |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    希望将来能够更好地支持更高种类的类型。这种特殊情况可能通过使用几乎完成generic associated types功能在夜间有一个解决方案,但我还没有找到它。

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

    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 案例仍然无法编译并且确实无法工作,因为您将返回对本地值的引用。

    【讨论】:

    • 嘿@kmdreko - 感谢您的回复!这条线“自我的生命周期比调用者可以选择的任何生命周期都要短”这句话很棒,我学到了一些新东西。我从另一部分得到灵感:“不幸的是,没有办法在多个约束中使用相同的 for。”也许可以通过辅助特征将多个限制嵌套到单个约束中? Playground 任何关于为什么助手不够通用的见解?
    • @moatra 你的坚持得到了回报!您的尝试揭示了这个问题:HRTBs: "implementation is not general enough", but it is 哪些文档表明,如果不立即将通用闭包生命周期推导出为 Fn* 类型(闭包 -> FnHelper -> FnOnce),它确实适用于函数! This 工作和编译。考虑到这一点,我将不得不重新考虑并修改我的答案。
    • 不错的发现!很高兴知道它可以使用正确注释的函数来完成,但是能够使用闭包并让编译器处理推理肯定会很好。再次感谢您的帮助!
    【解决方案2】:

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

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

    所以在中等待效果是没有意义的,你仍然必须在使用它时等待它 - 并且直到你真正在 chain 之外等待它,什么都不会发生——future 就是这样懒惰的。

    TL;DR 我会安排你的 effect 方法来返回值并安排 chain 只返回这些值

    【讨论】:

    • 嘿@Scherzo - 感谢您的帮助! future.then() 链接很好地提醒我我需要熟悉那个库。在一般情况下,您关于在链中等待未来的观点通常是无益的。在我的特殊情况下,我需要保留计算结果并将相同的结果返回给客户端。但是,如果持久化失败,我需要向客户端返回错误响应。我将深入研究这些文档,看看是否有什么让我兴奋的地方,但我很喜欢这个练习。
    • @moatra 您提出的“.await”的结果?展开一个值,但它也是一个结果。为什么?因为异步函数(如您调用的效果)返回您作为返回类型提供的值的结果:因此效果真正的作用是返回类型 Result,E1>。如果你说你想发送关于失败计算的错误,你仍然需要处理它——目前你只处理等待/异步本身的潜在错误。
    • Playground 展示了我想要完成的工作。参见第 32 和 168 行的 cmets
    猜你喜欢
    • 1970-01-01
    • 2022-08-22
    • 1970-01-01
    • 2013-07-03
    • 2017-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多