简体   繁体   English

Rust 异步借用生命周期

[英]Rust Async Borrow Lifetimes

I'm trying to make a helper that allows asynchronously chaining side effects, but I'm unable to get the generic bounds correct so that the compiler understands the output of the future outlives a reference used to build it.我正在尝试制作一个允许异步链接副作用的助手,但我无法正确获取通用边界,以便编译器理解未来的 output 比用于构建它的参考更有效。

Playground Link 游乐场链接

The gist of it comes down to:它的要点归结为:

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!()
    }
}

gives a compiler error of给出编译器错误

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`

If we fix up chain so that it can infer that the reference is available for the same lifetime as the future:如果我们修复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)
    }
}

We get a new compiler error about moving self.data while it's still borrowed.当 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

I guess there's a pathological closure along the lines of |this| futures::future::ready(Err(this))我想有一个沿着|this| futures::future::ready(Err(this))行的病态闭合。 |this| futures::future::ready(Err(this)) that would cause an early return with the borrow still "alive". |this| futures::future::ready(Err(this))这将导致提前返回,借入仍然“活着”。

Question问题

How can we get chain to work?我们怎样才能让chain工作? My normal lifetime trick of block-scoping doesn't seem to help.我正常的块作用域技巧似乎没有帮助。 Is there a set of where constraints that can be added to prove that the borrow and then eventual move are on disjoint lifetimes?是否可以添加一组where约束来证明借用和最终移动的生命周期不相交?

It looks like you are trying to implement future.then()看起来您正在尝试实现future.then()

If you are aware of that and you are doing it as an exercise, you probably should design it in a way that the effect method returns values, and use these values to return from chain method.如果您意识到这一点并且将其作为练习进行,您可能应该将其设计为效果方法返回值,并使用这些值从方法返回。 That way you enforce proper order of operations.这样你就可以强制执行正确的操作顺序。 As far as I understand your design, you do not benefit from awaiting on effect inside the chain method, since as your skip function is also async and will return future (the actual return type of chain method is Future<Output=Result<T, E>>, since async works that way: it wraps your explicit return type in future).据我了解您的设计,您不会从等待方法中的效果中受益,因为您的跳过function 也是异步的并且将返回未来(方法的实际返回类型是 Future<Output=Result<T, E>>,因为 async 是这样工作的:它在将来包装你的显式返回类型)。

So there is no point in awaiting on the effect inside the chain , you still have to await it whenever you use it - and nothing will happen until you actually await for it outside of the chain - futures are lazy that way.因此,在内等待效果是没有意义的,无论何时使用它,你仍然必须等待它——在你真正在外等待它之前,什么都不会发生——期货就是这样懒惰的。

TL;DR I would arange your effect methods to return values and arrange chain to just return these values TL;DR 我会安排您的效果方法以返回值并安排以仅返回这些值

This particular situation is one where the current constraint syntax and lack of higher-kinded types does not let you express what you want.这种特殊情况是当前约束语法和缺乏更高种类的类型不允许您表达您想要的内容。

You can use a higher-rank trait bound , the for<'a> syntax, to introduce an intermediate generic lifetime parameter 'a within a where clause to dictate that the constraint must be valid for any lifetime.您可以使用更高级别的 trait bound ,即for<'a>语法,在where子句中引入中间通用生命周期参数'a来指示约束必须在任何生命周期内都有效。 This is necessary here and the reason your first fix didn't work was because 'a as a generic on chain meant that the lifetime was determined by the caller , however, lifetime of self is by construction less than any lifetime that could be picked by the caller.这是必要的,你的第一个修复不起作用的原因是因为'a作为chain上的泛型意味着生命周期是由调用者决定的,但是, self的生命周期是由构造小于可以选择的任何生命周期呼叫者,召集者。 So the slightly more correct syntax (and identical to the de-sugared original code) would be:所以稍微更正确的语法(并且与去糖的原始代码相同)将是:

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
{
    ...

But this doesn't help at all, since there is still no association between Fut and 'a .但这根本没有帮助,因为Fut'a之间仍然没有关联。 There's unfortunately no way to use the same for<'a> across multiple constraints.不幸的是,没有办法在多个约束中使用相同for<'a> You could try using impl Trait to define it all at once, but that isn't supported:您可以尝试使用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)
   |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

There will hopefully be better support for higher-kinded types in the future.希望将来对更高种类的类型有更好的支持。 This particular case might have a solution on nightly by using the almost-complete generic associated types feature, but I haven't yet found it.通过使用几乎完整的通用关联类型功能,这种特殊情况可能在每晚都有解决方案,但我还没有找到它。

So the only real fix then is to use a named type as the return value, which really only leaves us with trait objects:所以唯一真正的解决方法是使用命名类型作为返回值,这实际上只给我们留下了 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;

As a side note, your bad case still does not compile and indeed cannot work, since you'd be returning a reference to a local value.附带说明一下,您的bad案例仍然无法编译并且确实无法工作,因为您将返回对本地值的引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM