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?
Vector is a module in Rust that provides the container space to store values. It is a contiguous resizable array type, with heap-allocated contents. It is denoted by Vec<T>. Vectors in Rust have O(1) indexing and push and pop operations in vector also take O(1) complexity.
In Rust, there are several ways to initialize a vector. In order to initialize a vector via the new() method call, we use the double colon operator: let mut vec = Vec::new();
To remove all elements from a vector in Rust, use . retain() method to keep all elements the do not match. let mut v = vec![
How to access vector values. Rust provides us with two methods to access elements in a vector. We can access values with either the indexer, or with the get() method.
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
.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.
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