3

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
micoay
  • 63
  • 7
  • 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 Apr 25 '21 at 23:37
  • 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 Apr 25 '21 at 23:41
  • 1
    `take()` is a nice solution IMHO, but note that a more idiomatic name for the method would be `into_stdout()` - in Rust the `into_` 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 Apr 26 '21 at 14:47

1 Answers1

2

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366