Reputation: 93
I have a struct UI
holding a mutable reference to Stdout
. Instead of mutating it on update, I'd prefer to replace it with an entirely new 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(())
}
The borrow checker complains that stdout is borrowed mutably twice:
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<'_>`
There are two weird behaviors:
v
, the error goes away.ui.display()?
, the error also goes away.What's the problem here?
Addressing @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
implementsDrop
, so that it can deallocate its contents when destroyed; and thereforeUI
automatically implementsDrop
, which means there is code executed which could accessstdout
between reborrowing it for a newUI
and assigning it toui
.
Then why does this return an error?
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.
Upvotes: 3
Views: 234
Reputation: 432139
When replacing a value, the new value must be created first:
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
This means that your two UI
structs would attempt to coexist. Since they both have a mutable reference to stdout
, the Drop::drop
implementation might mutate stdout
, which would violate the rules of references as there would be multiple active mutable references at one point.
When you don't call write
, the non-lexical borrow checker sees that the borrow isn't needed, so there's no problem.
Then why does [explicitly dropping the value before reassigning it] return an error?
Because that is a limitation of the current borrow checker implementation.
See also:
Upvotes: 2