Is there any way to mitigate a 'borrow may still be in use when generator yields' error in nested generators by using lifetimes?

huangapple go评论64阅读模式
英文:

Is there any way to mitigate a 'borrow may still be in use when generator yields' error in nested generators by using lifetimes?

问题

我有一个Rust生成器,它引用了&self,但我收到一个错误消息,说borrow may still be in use when generator yields出现在let mut g = t.inner()这一行上 - 具体来说是对t的引用。

从我所了解的情况看,编译器无法保证self的生命周期超过生成器,尽管我已经添加了生命周期。有没有正确的方法来做这件事,或者这只是生成器的一个限制?

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=25a8034c25c248457c348729d9f363a6

#![feature(generators, generator_trait)]

use std::{ops::Generator, pin::Pin};

struct T(i32);

impl T {
    // 嵌套的生成器和`&self`很重要。
    // 将其更改为`self`并删除生命周期会修复错误。
    // 可以省略生命周期,但为了清晰起见,添加了它。
    
    fn inner&lt;'a>(&'a self) -> impl Generator<(), Yield = i32, Return = ()> + Unpin + 'a {
        move || {
            // 在实际示例中,接收者类型是`&amp;mut self`,所以我们需要
            // 假装我们在生成器中有一个对self的引用。
            // 实际示例太大,无法包含在此处。
            let _ = self.0;
            
            yield 0;
        }
    }
}

fn main() {
    let t = T(10);
    
    // 如果取消注释这个引用,它会以某种方式编译通过?
    // let t = &amp;t;
    
    let mut g = move || {
        
        // 另一方面,这只是移动错误,不会做任何其他事情
        // let t = &amp;t;
    
        // 错误出现在对`t`的隐式引用上
        // 如果将其更改为`let mut g = (&amp;t).inner();`,则更清晰
        let mut g = t.inner();

        // 假装处理生成器的结果。
        let _ = Pin::new(&mut g).resume(());
        yield 0;
    };
    
    let _ = Pin::new(&mut g).resume(());
}
英文:

I have a rust generator which has a reference to a &amp;self, but I'm getting an error that says borrow may still be in use when generator yields on the let mut g = t.inner() line - specifically the reference to t.

From what I can tell, the compiler can't guarantee t outlives the generator, despite the lifetimes I've added. Is there a proper way to do this, or is this just a caveat of generators?

https://play.rust-lang.org/?version=nightly&amp;mode=debug&amp;edition=2021&amp;gist=25a8034c25c248457c348729d9f363a6

#![feature(generators, generator_trait)]

use std::{ops::Generator, pin::Pin};

struct T(i32);

impl T {
    // The nested generator and `&amp;self` are important.
    // Changing it to `self` and removing the lifetime fixes the error.
    // Could elide the lifetime, but it&#39;s there for clarity.
    
    fn inner&lt;&#39;a&gt;(&amp;&#39;a self) -&gt; impl Generator&lt;(), Yield = i32, Return = ()&gt; + Unpin + &#39;a {
        move || {
            // In the real example, the receiver type is `&amp;mut self` so we need
            // to pretend we have a reference to self in the generator.
            // The real example is too large to include here.
            let _ = self.0;
            
            yield 0;
        }
    }
}

fn main() {
    let t = T(10);
    
    // Uncommenting this ref lets it compile for some reason?
    // let t = &amp;t;
    
    let mut g = move || {
        
        // On the other hand, this doesn&#39;t do anything other than moving the error
        // let t = &amp;t;
    
        // The error is on the implicit reference to `t`
        // More clear if it&#39;s changed to `let mut g = (&amp;t).inner();`
        let mut g = t.inner();

        // Pretend to do something with the generator&#39;s results.
        let _ = Pin::new(&amp;mut g).resume(());
        yield 0;
    };
    
    let _ = Pin::new(&amp;mut g).resume(());
}

答案1

得分: 2

这是因为生成器被编译成状态机,当它们yield时会改变它们的状态。在你的例子中,因为你在g内部移动了t,所以隐式的借用&amp;t在使用t.inner()时被占用,需要借用生成器。然而,当你yield时,你也需要对生成器进行可变借用,这是不允许的。

这个问题可以通过不让生成器拥有t的所有权来解决,这样就需要对生成器进行不可变借用以获得&amp;t,而不是拥有&amp;t的所有权,这就是当你写let t = &amp;t时发生的情况。

你也可以通过从g的定义中移除move来获得相同的行为:

let mut g = || {
    // 错误出现在对 `t` 隐式引用上
    // 如果改成 `let mut g = (&amp;t).inner();` 就更明显了
    let mut g = t.inner();
    // 假装对生成器的结果做了些什么。
    let _ = Pin::new(&amp;mut g).resume(());
    yield 0;
};
英文:

This is because generators are compiled to state machines that change their state when they yield. In your example, since you move t within g, the (implicit) borrow &amp;t that is taken with t.inner() requires borrowing the generator. However, when you yield, you also need to mutable borrow the generator, which is not allowed.

This can be solved by not making the generator take the ownership of t, which then requires borrowing immutably the generator to get &amp;t, but instead take the ownership of &amp;t, which is what happens when you let t = &amp;t.

You could also obtain the same behaviour by removing move from the definition of g:

let mut g = || {
    // The error is on the implicit reference to `t`
    // More clear if it&#39;s changed to `let mut g = (&amp;t).inner();`
    let mut g = t.inner();
    // Pretend to do something with the generator&#39;s results.
    let _ = Pin::new(&amp;mut g).resume(());
    yield 0;
};

huangapple
  • 本文由 发表于 2023年6月1日 02:53:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76376508.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定