即使在放弃第一次借用之后,“不能多次借用可变”

问题描述

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>,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

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

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

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

我哪里出错了?

解决方法

变化:

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>,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


说明

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

如果你有一个包含引用的容器,我们称之为Container<'a>,那么容器的生命周期是多少?它与它的引用具有相同的生命周期,因为容器的生命周期不能超过它所包含的内容。我们将该生命周期称为 'a。非常重要:'a 中的 Container<'a> 代表容器的整个生命周期。因此,当您编写带有 &'a self&'a mut self 接收器的方法时,您与编译器通信的是“为了调用此方法,该方法必须借用容器以获取其剩余部分整个一生。”现在你真正想要的是什么时候?几乎从来没有!您需要编写一个只能被精确调用一次的方法是非常罕见的,因为它在 self 的剩余生命周期中永久借用 self。因此,&'a self&'a mut self 是新手陷阱,避免使用它们。

澄清

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

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

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

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

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

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