使用可变引用覆盖变量时借用检查器错误

Borrow checker error when overwriting variable with mutable reference

我有一个结构 UI 持有对 Stdout 的可变引用。与其在更新时改变它,我更愿意用一个全新的 UI:

替换它
use std::io::{stdout, Result, Stdout, Write};

struct UI<'s> {
    stdout: &'s mut Stdout,
    v: Box<()>, // If remove this field, the error goes away.
}

impl<'s> UI<'s> {
    fn new(stdout: &'s mut Stdout) -> Result<Self> {
        let ui = UI {
            stdout: stdout,
            v: Box::new(()),
        };

        Ok(ui)
    }

    fn write(&mut self) -> Result<()> {
        write!(self.stdout, "heyyyyy")?;
        self.stdout.flush()?;

        Ok(())
    }
}

fn main() -> Result<()> {
    let mut stdout = stdout();
    let mut ui = UI::new(&mut stdout)?;

    ui = UI::new(&mut stdout)?;
    ui.write()?; // If you comment this line out, the error goes away.

    Ok(())
}

playground

借用检查器抱怨 stdout 被可变地借用了两次:

error[E0499]: cannot borrow `stdout` as mutable more than once at a time
  --> src/main.rs:30:18
   |
28 |     let mut ui = UI::new(&mut stdout)?;
   |                          ----------- first mutable borrow occurs here
29 | 
30 |     ui = UI::new(&mut stdout)?;
   |     --           ^^^^^^^^^^^ second mutable borrow occurs here
   |     |
   |     first borrow might be used here, when `ui` is dropped and runs the destructor for type `UI<'_>`

有两个奇怪的行为:

  1. 如果我删除字段 v,错误就会消失。
  2. 如果我删除 ui.display()?,错误也会消失。

这里有什么问题?

寻址 @kmdreko's suggestion:

The first point is explained in the error message, albeit in a roundabout way if you don't know what's going on. Box implements Drop, so that it can deallocate its contents when destroyed; and therefore UI automatically implements Drop, which means there is code executed which could access stdout between reborrowing it for a new UI and assigning it to ui.

那为什么这个 return 出错了?

fn main() -> Result<()> {
    let mut stdout = stdout();
    let mut ui = UI::new(&mut stdout)?;

    for _ in 0..10 {
        drop(ui);

        ui = UI::new(&mut stdout)?;
        ui.write()?;
    }
    
    Ok(())
}
error[E0499]: cannot borrow `stdout` as mutable more than once at a time
  --> src/main.rs:33:22
   |
28 |     let mut ui = UI::new(&mut stdout)?;
   |                          ----------- first mutable borrow occurs here
...
33 |         ui = UI::new(&mut stdout)?;
   |         --           ^^^^^^^^^^^ second mutable borrow occurs here
   |         |
   |         first borrow might be used here, when `ui` is dropped and runs the destructor for type `UI<'_>`

Someone on Reddit suggested to implement take(self) -> &'s mut Stdout for UI and it works, but I have no idea why. Playground.

替换值时,必须先创建新值:

struct Noisy(u8);

impl Noisy {
    fn new(v: u8) -> Self {
        eprintln!("creating {}", v);
        Self(v)
    }
}

impl Drop for Noisy {
    fn drop(&mut self) {
        eprintln!("dropping {}", self.0);
    }
}

fn main() {
    let mut ui = Noisy::new(1);
    ui = Noisy::new(2);
}
creating 1
creating 2
dropping 1
dropping 2

这意味着您的两个 UI 结构将尝试共存。由于它们都有对 stdout 的可变引用,Drop::drop 实现 可能 发生变异 stdout,这将违反引用规则一次有多个活动的可变引用。

当您不调用 write 时,非词法借用检查器认为不需要借用,所以没有问题。

Then why does [explicitly dropping the value before reassigning it] return an error?

因为那是a limitation of the current borrow checker implementation.

另请参阅:

  • What are non-lexical lifetimes?
  • Moved variable still borrowing after calling `drop`?