【问题标题】:"cannot borrow as mutable more than once" even after dropping the first borrow即使放弃第一次借用,“不能多次借用可变”
【发布时间】:2021-02-08 15:17:23
【问题描述】:
trait Output {
    fn write(&mut self, text: &str);
}

struct DummyOutput {}

impl Output for DummyOutput {
    fn write(&mut self, text: &str) {
        // self does not need to be mut in this reproducer,
        // but it would in the real version
        println!("{}", text);
    }
}

enum EncoderOutput<'a, T> {
    Owned(T),
    Borrowed(&'a mut T),
}

impl<'a, T> AsMut<T> for EncoderOutput<'a, T> {
    fn as_mut(&mut self) -> &mut T {
        match self {
            EncoderOutput::Owned(ref mut o) => o,
            EncoderOutput::Borrowed(b) => b,
        }
    }
}

struct Encoder<'a, O: Output> {
    output: EncoderOutput<'a, O>,
}

impl<'a, O: Output> Encoder<'a, O> {
    // here's the idea:
    // every child instance will have a borrowed output,
    // and always only one level of indirection, i.e.:
    // - root: "&mut O" or "O"
    // - child1: "&mut O"
    // - child2: "&mut O"
    // but never:
    // - childN: "&mut &mut O"
    fn child(&'a mut self) -> Self {
        Encoder {
            output: EncoderOutput::Borrowed(self.output.as_mut()),
        }
    }
}

fn main() {
    let mut enc1 = Encoder {
        output: EncoderOutput::Owned(DummyOutput {}),
    };

    {
        // I know this borrows mutably from enc1
        let mut enc2 = enc1.child();

        // so this will obviously not work:
        // enc1.output.as_mut().write("bar 2b");

        // but this does work:
        enc2.output.as_mut().write("bar 2a");
    } // but then the borrow "enc2" should be dropped here?

    // so why does this fail with:
    // "cannot borrow [...] as mutable more than once"
    enc1.output.as_mut().write("bar 3");
}
error[E0499]: cannot borrow `enc1.output` as mutable more than once at a time
  --> src/main.rs:68:5
   |
57 |         let mut enc2 = enc1.child();
   |                        ---- first mutable borrow occurs here
...
68 |     enc1.output.as_mut().write("bar 3");
   |     ^^^^^^^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

我的直觉告诉我这失败了,因为 child() 返回的 Encoder 中借用的生命周期与“父”具有相同的生命周期 - 但我看不到一种解耦生命周期的方法,即使返回值的生命周期显然应该小于或等于父值,因为它可以预先删除。

我也尝试过没有EncoderOutput 的版本,只是在Encoder 中直接有一个&amp;mut O,但问题是一样的。

我的思路为什么这应该起作用:当main() 中的范围结束时,enc2 被删除,隐含的Drop impl 运行它,这会清理EncoderOutput::Borrowed 和其中的引用,因此没有其他 &amp;mut 引用到 enc1,并且 enc1 可以再次可变借用。

我哪里错了?

【问题讨论】:

标签: generics rust lifetime borrow-checker borrowing


【解决方案1】:

变化:

fn child(&'a mut self) -> Encoder<'a, O>;

收件人:

fn child(&mut self) -> Encoder<'_, O>;

修正编译示例:

trait Output {
    fn write(&mut self, text: &str);
}

struct DummyOutput {}

impl Output for DummyOutput {
    fn write(&mut self, text: &str) {
        println!("{}", text);
    }
}

enum EncoderOutput<'a, T> {
    Owned(T),
    Borrowed(&'a mut T),
}

impl<'a, T> AsMut<T> for EncoderOutput<'a, T> {
    fn as_mut(&mut self) -> &mut T {
        match self {
            EncoderOutput::Owned(ref mut o) => o,
            EncoderOutput::Borrowed(b) => b,
        }
    }
}

struct Encoder<'a, O: Output> {
    output: EncoderOutput<'a, O>,
}

impl<'a, O: Output> Encoder<'a, O> {
    // line below changed from:
    // fn child(&'a mut self) -> Encoder<'a, O> {
    // to:
    // child(&mut self) -> Encoder<'_, O> {
    fn child(&mut self) -> Encoder<'_, O> {
        Encoder {
            output: EncoderOutput::Borrowed(self.output.as_mut()),
        }
    }
}

fn main() {
    let mut enc1 = Encoder {
        output: EncoderOutput::Owned(DummyOutput {}),
    };

    {
        let mut enc2 = enc1.child();
        enc2.output.as_mut().write("bar 2a");
    }

    enc1.output.as_mut().write("bar 3");
}

playground


说明

&amp;'a self&amp;'a mut self 是所有 Rust 中最常见的生命周期陷阱,大多数初学者甚至中级 Rustaceans 最终都会陷入其中。我一看到你的例子中的那一行就知道是错误的,甚至没有试图理解你的其余代码的任何其他内容。超过 99.9% 的时间 &amp;'a self&amp;'a mut self 是错误的,当你看到它们时,它应该会发出一个很大的危险信号,当你看到它们时,你应该积极地重构它们。好的,既然如此,这就是它们如此糟糕的原因:

如果你有一些包含引用的容器,我们称之为Container&lt;'a&gt;,那么容器的生命周期是多少?它的生命周期与其引用的生命周期相同,因为容器不能超过它所包含的生命周期。让我们称之为生命'a。非常重要:Container&lt;'a&gt; 中的'a 代表容器的整个生命周期。因此,当您使用&amp;'a self&amp;'a mut self 接收器编写方法时,您与编译器通信的是“为了调用此方法,该方法必须在其整个生命周期的剩余时间内借用容器。” 现在这是你真正想要的东西是什么时候?几乎从来没有!极少需要编写一个只能被调用一次的方法,因为它在self 的剩余生命周期中永久借用self。因此,&amp;'a self&amp;'a mut self 是新手陷阱,请避开它​​们。

澄清

&amp;'a self&amp;'a mut self 只是当'a 代表self 本身的整个生命周期时的危险信号。在像下面这样的场景中,'a 的范围仅限于一个方法,那么就可以了:

// 'a only local to this method, this is okay
fn method<'a>(&'a self) {
    // etc
}

【讨论】:

  • 我觉得这发送了错误的信息,如果无法推断其他生命周期,有时需要注释 &amp;self 的生命周期。孤立的 &amp;'a self 构造并不是危险信号(例如,它广泛用于 str 上的方法)。它的 &amp;'a SomeType&lt;'a&gt; 构造几乎肯定是错误的,其中引用的生命周期绑定到容器的通用生命周期。
  • @kmdreko 感谢您的评论。我在回答中添加了说明。
  • 感谢您的帮助!我发布的代码显然只是摘录,但您的洞察力也帮助我找出了原始问题。在那里我还处理了一个盒装的未来,所以虽然我不得不用一个生命周期来约束&amp;mut self,但我通过明确指定一个与selfs 生命周期无关的来让它工作。例如:impl&lt;'a&gt; X&lt;'a&gt; { fn recursive&lt;'a, 'b&gt;(&amp;'b mut self, encoder: &amp;'b mut Encoder&lt;'a&gt;) -&gt; BoxFuture&lt;'b, ()&gt; { ... } } - 如果它可以帮助其他人:)
【解决方案2】:

经过一番折腾,解决办法是将child改为:

    fn child(&mut self) -> Encoder<'_, O> {
        Encoder {
            output: EncoderOutput::Borrowed(self.output.as_mut()),
        }
    }

现在我知道问题在于一种方法具有生命周期,因此 rustc 推断主题(自我)必须保持借用的时间不超过它的生命周期,因此一旦调用该方法,你就会“锁定”它。遗憾的是,我不记得确切的问题或找到它以前的实例,尽管我知道它们存在。

所以虽然我能想到可能的解释,但因为我不知道它们是否正确,我不会提供任何解释,抱歉。但基本上通过使用'_,你是在告诉 rustc 给你一个新的输出生命周期并找出界限。

【讨论】:

    猜你喜欢
    • 2023-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-15
    • 1970-01-01
    • 2020-04-25
    相关资源
    最近更新 更多