Consider a toy problem in which I have a Node
struct that represents nodes of linked lists and that I want to create a function that builds a list with values from 1 to 9. The following code works as expected:
struct Node {
val: i32,
next: Option<Box<Node>>,
}
fn build_list() -> Option<Box<Node>> {
let mut head = None;
let mut tail = &mut head;
for n in 1..10 {
*tail = Some(Box::new(Node {val: n, next: None}));
if let Some(ref mut x) = tail {
tail = &mut x.next;
};
}
head
}
But if I modify the match expression in the build_list
function to the following, it fails to compile:
fn build_list() -> Option<Box<Node>> {
let mut head = None;
let mut tail = &mut head;
for n in 1..10 {
*tail = Some(Box::new(Node {val: n, next: None}));
if let Some(x) = tail.as_mut() {
tail = &mut x.next;
};
}
head
}
The compilation error:
error[E0506]: cannot assign to `*tail` because it is borrowed
--> src/main.rs:72:9
|
72 | *tail = Some(Box::new(Node {val: n, next: None}));
| ^^^^^
| |
| assignment to borrowed `*tail` occurs here
| borrow later used here
73 | {
74 | if let Some(x) = tail.as_mut() {
| ---- borrow of `*tail` occurs here
error[E0499]: cannot borrow `*tail` as mutable more than once at a time
--> src/main.rs:74:30
|
74 | if let Some(x) = tail.as_mut() {
| ^^^^ mutable borrow starts here in previous iteration of loop
In this example, what is the difference between
if let Some(ref mut x) = tail
and
if let Some(x) = tail.as_mut()
?
(As a beginner learning Rust) I was expecting those match expressions to be equivalent, but apparently there's some subtle difference that I'm missing.
I cleaned up the code from my original example so that I don't need a placeholder element for the head of the list. The difference (and compilation error) still remains, I just get an additional compilation error for assigning to borrowed *tail
.
(This is just a curious observation, doesn't help answering the original question)
After considering @Emoun's answer, it sounded important (in the first working example) that the compiler should be able to know that tail
was changing at every iteration of the loop (such that it could make sure &mut x.next
being borrowed each time was different). So I made an experiment to change the code in a way that the compiler wouldn't be able to tell if that was the case by adding a if n % 2 == 0
condition to the tail = &mut x.next;
assignment. Sure enough, it resulted in a compilation error similar to the other one:
fn build_list() -> Option<Box<Node>> {
let mut head = None;
let mut tail = &mut head;
for n in 1..10 {
*tail = Some(Box::new(Node {val: n, next: None}));
if let Some(ref mut x) = tail {
if n % 2 == 0 {
tail = &mut x.next;
}
};
}
head
}
The new error:
error[E0506]: cannot assign to `*tail` because it is borrowed
--> src/main.rs:60:9
|
60 | *tail = Some(Box::new(Node {val: n, next: None}));
| ^^^^^
| |
| assignment to borrowed `*tail` occurs here
| borrow later used here
61 | if let Some(ref mut x) = tail {
| --------- borrow of `*tail` occurs here
error[E0503]: cannot use `*tail` because it was mutably borrowed
--> src/main.rs:61:16
|
61 | if let Some(ref mut x) = tail {
| ^^^^^---------^
| | |
| | borrow of `tail.0` occurs here
| use of borrowed `tail.0`
| borrow later used here
error[E0499]: cannot borrow `tail.0` as mutable more than once at a time
--> src/main.rs:61:21
|
61 | if let Some(ref mut x) = tail {
| ^^^^^^^^^ mutable borrow starts here in previous iteration of loop
The reason why the second version of your code fails is because rust's methods/function always borrow whole objects and never parts of them.
What this means in your case is that the tail.as_mut()
borrows tail
mutably and that this borrow will stay in effect as long as tail
is being used:
...
for n in 1..10 {
*tail = Some(Box::new(Node {val: n, next: None})); // Error in the second iteration,
// 'tail' was already borrowed
if let Some(x) = tail.as_mut() { // <-+ Borrow of 'tail' starts in the first iteration
tail = &mut x.next; // <-+ 'tail' now borrows itself
}; // |
} // <-+ Borrow of 'tail' ends here, after the last iteration
...
Since x
is a borrow of tail
, &mut x.next
is also a borrow of tail
, which means tail = &mut x.next
is tail
borrowing itself. Therefore, the initial borrow of tail
cannot go out of scope as long as tail
is in scope. tail
is used in every iteration, so the borrow can only go out of scope after the last iteration of the loop.
Now, why does the first version of build_list
work? In short: because tail
is never borrowed. if let Some(ref mut x) = tail
is a destructuring of tail
into its components (in this case the Option::Some
and x
). This doesn't borrow tail
as a whole, it just borrows x
. When you then tail = &mut x.next
, you now also destructure x
into its components (extracting only next
), and borrow that using tail
. In the next iteration, tail
is not borrowed and can therefore happily be reassigned.
Method/function calls are limited in that they don't know which parts of an object you will use later. Therefore, as_mut()
has to borrow the whole tail
, even though you are only using part of it. This is a limitation with the type system and is one reason why getter/setter methods are weaker/more limiting than calling a struct's members directly: getters/setters will force you to borrow the whole struct, while accessing a member directly will only borrow that member and not the others.
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