Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How borrow as mutable vs immutable in Rust?

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?

like image 446
Seph Reed Avatar asked Feb 20 '20 16:02

Seph Reed


2 Answers

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.

like image 74
Ejdrien Avatar answered Sep 20 '22 15:09

Ejdrien


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 Copyable, 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

like image 33
Brian Avatar answered Sep 18 '22 15:09

Brian