I've read these docs: https://doc.rust-lang.org/rust-by-example/scope/borrow/mut.html
I've also read this question: (Cannot borrow immutable borrowed content as mutable)
The docs helped me understand how to declare borrowing as mutable (I think):
let mut (part1, part2) = someTuple;
But I'm haven't been able to find explicit instructions on what borrowing as an immutable looks like. This is my guess:
let (part1, part2) = someTuple;
I know this is a super basic question, but Googling it took me way off into the deep end of explanations and I'm still trying to get my bearings in the most simple of contexts.
How do I borrow as mutable vs an immutable in Rust?
let x = 0;
let immutable_borrow = &x; //borrow as immutable
//to borrow as mutable the variable needs to be declared as mutable
let mut y = 1;
let mutable_borrow = &mut y; //borrow as mutable
Note 1: you can borrow a variable either as immutable or mutable in the same scope, meaning you can't do this:
let mut x = 0;
let immutable_borrow = &x;
let mutable_borrow = &mut x;
Why?
Because if you would have mutable and immutable references of the same variable, then the data of that variable could change through that mutable reference and that could cause a lot of problems.
Note 2: you can immutably borrow a variable endless times but you can mutably borrow a variable only once.
//You can do this
let x = 0;
let x1 = &x;
let x2 = &x;
//...
//But you can't do this
let mut y = 0;
let y1 = &mut y;
let y2 = &mut y; //won't compile if you use y1 after you declare y2
Why?
As mentioned above. One mutable reference could change the data all the other mutable references are poiting to without them knowing. That could cause a lot of problems. But having multiple immutable references is okay, because the data can't be unexpectedly changed.
Ejdrien answers demonstrates the difference between mutable and immutable borrows, however it does not address a subtitle in your question, which is that you are performing borrows as part of pattern matching.
When you write
let (mut part1, mut part2) = someTuple;
you are binding the contents of someTuple
to the mutable variables part1
and part2
by moving them. Unless the contents of someTuple
were Copy
able, the variables part1
and part2
become the exclusive owners of their respective values. If you attempt to access someTuple
later by writing, e.g.
println!("{}", someTuple.0);
you'll receive a compile error from the borrow checker that resembles
error[E0382]: borrow of moved value: `someTuple.0`
--> main.rs:6:20
|
4 | let (mut part1, mut part2) = someTuple;
| --------- value moved here
5 |
6 | println!("{}", someTuple.0);
| ^^^^^^^^^^^ value borrowed here after move
In this particular context, there are two ways to inform the compiler that we want to only borrow the contents of someTuple
. The first is the technique that Ejdrien described, which is explicitly borrowing the tuple and the performing the pattern matching against then resulting reference:
// Produce two mutable references
let (part1, part2) = &mut someTuple;
// Produce two immutable references
let (part1, part2) = &someTuple;
The problem with this approach is that we are forced to borrow everything in the same way. What if we only want a mutable reference to someTuple.0
, and want to retrieve someTuple.1
as a copy, or as an immutable reference? For the tuple example here, this may not seem too critical, but for more complex cases of pattern matching, having this type of control is much more important.
This brings us two the second solution: binding references. Instead of the above, we can write
// Produce two mutable references
let (ref mut part1, ref mut part2) = someTuple;
// Produce two immutable references
let (ref part1, ref part2) = someTuple;
Here, we explicitly state how we want to bind each variable in the pattern matching. The key here is that we are free to intermix mutable and immutable borrows, so the following is also entirely valid:
// Produce immutable reference and one mutable reference
let (ref part1, ref mut part2) = someTuple;
println!("{}", &someTuple.0); // Make a second immutable reference someTuple.0
*part2 = ... // Mutate someTuple.1
println!("{}", part1); // Continue using the immutable reference
If we swap the above with an explicit mutable borrow on the right hand side, we'll once again receive errors from the borrow checker due to simultaneous mutable and immutable references:
let (part1, part2) = &mut someTuple;
println!("{}", &someTuple.0);
*part2 = ... // Mutate someTuple.1
println!("{}", part1);
produces
error[E0502]: cannot borrow `someTuple.0` as immutable because it is also borrowed as mutable
--> main.rs:6:20
|
4 | let (part1,part2) =&mut someTuple;
| -------------- mutable borrow occurs here
5 |
6 | println!("{}", &someTuple.0);
| ^^^^^^^^^^^^ immutable borrow occurs here
...
9 | println!("{}", part1);
| ----- mutable borrow later used here
error: aborting due to previous error
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With