Reputation: 171
Consider this code (Rust Playground):
#[derive(Clone, Copy, Debug)]
struct X(i32);
impl std::ops::AddAssign for X {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
fn main() {
let mut ary_i32 = [1_i32; 2];
ary_i32[0] += ary_i32[1]; // OK
let mut ary_x = [X(1); 2];
ary_x[0] += ary_x[1]; // OK
let mut vec_i32 = vec![1_i32; 2];
vec_i32[0] += vec_i32[1]; // OK
let mut vec_x = vec![X(1); 2];
vec_x[0] += vec_x[1]; // error[E0502]: cannot borrow `vec_x` as immutable because it is also borrowed as mutable
}
Why I get E0502 only on vec_x
line?
I could not understand why only the operations for ary_x
and vec_i32
are permitted. Does borrow checker treat builtin types (i32
, array
) specially?
Upvotes: 10
Views: 822
Reputation: 171
I researched some resources and read MIR of my code, and managed to understand what is going on.
The comment by @trentcl will be the best answer.
I write the details as possible.
For array
, Index
and IndexMut
traits are not used and compiler directly manipulates array elements (you can see this with MIR). So, borrowing problem does not exist here.
Explanating for Vec
, rustc guide is useful.
First, Two-phase borrow is not applied to vec_foo[0] += vec_foo[1]
statement.
And, the difference between i32
and X
is caused by operator lowering.
Basically, statements like vec_user_defined[0] += vec_user_defined[1]
are converted to function calls like add_assign(index_mut(...), *index(...))
, and function arguments are evaluated from left to right. So, index_mut()
borrows x
mutably and index()
tries to borrow x
, and fails.
But for builtin types like i32
, compound assignment operator is not converted to function call, and rhs is evaluated before lhs (you can see index()
is called before index_mut()
with MIR). So, for builtin types, vec_builtin[0] += vec_builtin[1]
works.
I know these things from lo48576's article (Japanese).
I considered some workarounds:
Vec
to slice
as @trentcl said. But this doesn't work well for multidimensional Vec
.Upvotes: 7
Reputation: 475
Rust arrays live on the stack, are predictably sized, and therefore have stronger borrow checker guarantees. Vectors are smart pointers on the stack pointing at data that can grow and shrink on the Heap. Because the final example uses the Vector type, the borrow checker considers the entire Vector as a single mutably borrowed object when loading it from the Heap.
As you've observed, the borrow checker can create a mutable reference to a single element to something living on the Stack, whereas it creates a mutable reference to the Vector's smart pointer on the Stack, and then a further mutable reference to the data on the heap. This is why the immutable reference to vec_vec_x[1][1]
fails.
As @sebpuetz noted in a comment, you can solve this by first copying an immutable reference to vec_vec_x[1][1]
, then creating an immutable reference.
Upvotes: 1