Search code examples
structrustownership

Mutable and Immutable reference inside println


I am fairly new to Rust and encountered some beginner problems and want to share my solution with others around the internet that I haven't found in existing threads. I wonder if there is a more Rust approach to it.

I want to print out the height, width and area of a Rectangle.

struct Rectangle {
    height: u32,
    width: u32,
    area: u32,
}

impl Rectangle{
    fn area(& mut self) -> u32 {
        self.area = self.height * self.width;
        return self.area
    }
}

fn main() {
    let mut rect1 = Rectangle {height: 20, width: 30, area: 0};
    println!("Rectangle has height {} width {} and area {}", rect1.height, rect1.width, rect1.area());
}

which gives me the error cannot borrow as immutable because it is also borrowed as mutable

error[E0502]: cannot borrow `rect1` as mutable because it is also borrowed as immutable
  --> src/main.rs:17:88
   |
17 |     println!("Rectangle has height {} width {} and area {}", rect1.height, rect1.width, rect1.area());
   |     -----------------------------------------------------------------------------------^^^^^^^^^^^^--
   |     |                                                       |                          |
   |     |                                                       |                          mutable borrow occurs here
   |     |                                                       immutable borrow occurs here
   |     immutable borrow later used here

My solution

println!("Rectangle has height {1} width {2} and area {0}", rect1.area(), rect1.height, rect1.width);

changing the order in the println! statement.

I know that you cannot have an immutable and mutable reference at the same time, because the immutable reference does not expect the values to be changed. See here. But why does my solution work? Clearly, there still is a mutable and immutable reference in the same println! statement but with changed order.


Solution

  • But why does my solution work? Clearly, there still is a mutable and immutable reference in the same println! statement but with changed order.

    Nope! The thing is area() requires a mutable borrow but does not keep one, since it returns an owned value (u32) rather than a borrowed one (&u32), so the borrow only lasts for the extent of the call and is released once the call returns.

    Now you could expect the same of height and width, the trap is that println! implicitly borrows its parameters, so when you println!("{}", rect.height) it compiles to something along the lines of Arguments("", &rect.height), creating a borrow until the end of the formatting process.

    Now because the borrow is implicit you can't deref' the attribute (*rec.height) as that would be &*rec.height where rec.height is still a u8, however there is an other method which is block expressions:

    Blocks are always value expressions and evaluate the last expression in value expression context. This can be used to force moving a value if really needed.

    This means that &{rec.height} will first copy (or move) the value out of the structure, then borrow that copy. Therefore you can also fix the call by writing:

    println!("Rectangle has height {} width {} and area {}", {rect1.height}, {rect1.width}, rect1.area());
    

    That will copy the two attributes out first, then println! will implicitly borrow them, but it won't need to borrow the structure itself leading to three non-overlapping borrows.

    In that case, you may want to add a comment to explain why you're doing that as it's... odd.

    Then again, I think your area is an anti-pattern, so YMMV.