micoay
micoay

Reputation: 93

Borrow checker error when overwriting variable with mutable reference

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(())
}

playground

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:

  1. If I remove field v, the error goes away.
  2. If I remove 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 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.

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

Answers (1)

Shepmaster
Shepmaster

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

Related Questions