Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot add-assign within `Vec` of user-defined type

Tags:

rust

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?

like image 377
taotao Avatar asked Apr 21 '21 17:04

taotao


People also ask

What is a VEC in Rust?

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.

How do you initialize VEC in Rust?

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();

How do you clear a vector in Rust?

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 do you access vector elements in Rust?

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.


2 Answers

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:

  • Just use an intermediate variable as @sebpuetz said.
  • Convert Vec to slice as @trentcl said. But this doesn't work well for multidimensional Vec.
  • Write some macro to automatically introduce an intermediate variable. I found rhs_first_assign crate does such works.
like image 57
taotao Avatar answered Oct 21 '22 17:10

taotao


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.

like image 32
cryptograthor Avatar answered Oct 21 '22 15:10

cryptograthor