3
votes

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.

1
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.kmdreko
I'm not sure about the second though, it could be the compiler realizes that ui isn't used and therefore doesn't actually do the assignment, but that's just a guess.kmdreko
take() is a nice solution IMHO, but note that a more idiomatic name for the method would be into_stdout() - in Rust the into_<something> methods consume self and return the ownership of the thing inside that the object used to own. In this case you get back the original &mut stdout (with the original lifetime), which allows you to create a new ui that is "compatible" with the original one.user4815162342

1 Answers

2
votes

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: