How borrow as mutable vs immutable in Rust?
Asked Answered
C

2

9

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?

Churrigueresque answered 20/2, 2020 at 16:33 Comment(0)
J
9
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.

Jehius answered 20/2, 2020 at 16:38 Comment(2)
It's worth noting that thanks to lexicographical scoping, your last example does compile. The since y2 is declared after the last use of y1, the second mutable borrow is permitted as the scopes never overlap. This example would only be rejected by the borrow checker if you attempt to use y1 after the mutable borrow for y2. The same is true for your second example --in fact, all of the "illegal" borrows you list are perfectly allowed in Rust.Sappy
You mean NLL (non lexical lifetimes) not 'lexicographical scoping'?Swetlana
S
2

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

Sappy answered 20/2, 2020 at 17:40 Comment(4)
'Unless the contents of someTuple were Copyable' So if they're copyable it does one thing, but if not it does another? This seems like a potential gotcha.Churrigueresque
@SephReed That is correct. This distinction is explained in the documentation for the Copy. In brief, for each member of someTuple, if that member implements Copy both someTuple and the newly bound variables (e.g. part1 or part2) will have ownership of their respective copies data. If the member does not implement Copy, then ownership will be transferred to the new variable, and you will be permitted to access the data from someTuple`.Sappy
Are there alternative notations such that it is explicit whether you're using Copy or borrow?Churrigueresque
@SephReed I'm not sure I know what you mean. It is always explicit whether or not you are getting a pointer to something (aka "borrowing") or duplicating the thing itself (aka copying or moving). The distinction I referred to above applies only to the case where you are duplicating something, and the difference itself is whether or not the old owner remains valid (in the case of a copy) or invalid (in the case of a move). If there is a specific example of where you feel it is not clear whether a borrow or move is being performed, you may wish to pose a new question.Sappy

© 2022 - 2024 — McMap. All rights reserved.