Let's simplify and desugar your example:
fn main() {
let mut a: i32 = 1;
let b: &mut i32 = &mut a;
let c: &i32 = &*b;
println!("{:?},{:?}", b, c);
}
I think the source of confusion is that the prinln! macro looks like a regular function call but in reality isn't. If we replace the println! macro with a regular function:
fn func(format: &str, b: &mut i32, c: &i32) {}
fn main() {
let mut a: i32 = 1;
let b: &mut i32 = &mut a;
let c: &i32 = &*b;
func("{:?},{:?}", b, c);
}
Then we get the expected compiler error:
error[E0502]: cannot borrow `*b` as mutable because it is also borrowed as immutable
--> src/main.rs:7:5
|
6 | let c: &i32 = &*b;
| --- immutable borrow occurs here
7 | func("{:?},{:?}", b, c);
| ----^^^^^^^^^^^^^^^^^^^
| |
| mutable borrow occurs here
| immutable borrow later used by call
So the question now becomes why does println! work where func fails. Let's take a closer look at println! by expanding it:
fn main() {
let mut a: i32 = 1;
let b: &mut i32 = &mut a;
let c: &i32 = &*b;
{
::std::io::_print(::core::fmt::Arguments::new_v1(
&["", ",", "\n"],
&match (&b, &c) { // borrows b & c again here
(arg0, arg1) => [
::core::fmt::ArgumentV1::new(arg0, ::core::fmt::Debug::fmt),
::core::fmt::ArgumentV1::new(arg1, ::core::fmt::Debug::fmt),
],
},
));
};
}
The println! prepends & to its arguments, so if we revise func to do the same:
fn func(format: &str, b: &&mut i32, c: &&i32) {}
fn main() {
let mut a: i32 = 1;
let b: &mut i32 = &mut a;
let c: &i32 = &*b;
func("{:?},{:?}", &b, &c);
}
It now compiles.
So what have we learned here? If we mutably borrow some data, then immutably borrow from the mutable borrow, and then immutably borrow the mutable borrow and the immutable re-borrow of the mutable borrow, then there's no violation of Rust's ownership rules, since the immutable borrows are all borrowing from the same original mutable borrow which is not being mutated while the immutable borrows are alive.