9

I've been fighting with the borrow checker for a little bit... the gist of what I want to do is this:

#[derive(Debug)] struct SomeStruct { value: String, } impl SomeStruct { fn new(value: &str) -> SomeStruct { SomeStruct { value: value.to_string() } } fn change_value(&mut self, new_value: &str) { self.value = new_value.to_string(); } } fn main() { let mut my_vec = vec![ SomeStruct::new("foo"), SomeStruct::new("bar"), ]; my_vec[0].change_value(my_vec[1].value.as_str()); } 

This is a very generic version of a problem I'm having. Here's the stderr:

error[E0502]: cannot borrow `my_vec` as immutable because it is also borrowed as mutable --> src/main.rs:22:30 | 22 | my_vec[0].change_value(my_vec[1].value.as_str()); | ------ ^^^^^^ - mutable borrow ends here | | | | | immutable borrow occurs here | mutable borrow occurs here 

So the borrow checker doesn't allow me to borrow the vector twice (once as mutable, and then again as immutable), which I understand. But the frustrating thing is that I want to modify one element in the vector, and only read another. I'm new to Rust (surprise!), and I'm not sure I've wrapped my mind around all its details and design choices. But this is something that feels like it should work, yet doesn't. What am I missing, and what can I do to get this (or comparable behavior) to work? Any help is greatly appreciated!

0

3 Answers 3

12

I agree, this is slightly confusing. So let's first see, why this is not allowed by the Rust compiler.

Why it's not allowed

The index operator [] is something that can be overloaded, which means that users of the language can specify how it works. Rust tries to minimize the number of types of which the compiler has some special knowledge. As a consequence and despite its popularity, Vec<T> is just a normal type defined by a library. You could write your own Vec<T> without telling the compiler about it!

Vec<T> also overloads the index operator, to allow indexing a vector. But since the overload could do anything, it could always return the first element of the vector! And if you assume the index operator would do such a strange thing, this code shouldn't be allowed:

my_vec[0].change_value(my_vec[1].value.as_str()); 

Because my_vec[0] and my_vec[1] reference the same value.

How to make it work

Of course, the index operator is not implemented in such a stupid fashion and we know that. In order to get two references to different elements of the vector (where at least one is mutable), we have to use some special functions instead of the index operator. And there are quite some ways to do it:

I can't really tell you what method to use, because I don't know your exact use case. But just to fix your example, you can write:

let (head, tail) = my_vec.split_first_mut(); head.change_value(tail[0].value.as_str()); 
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! Very nice explanation. To make sure I understand: the .split methods create views into my Vec, yes? Thus the actual objects remain in the vector, but are changed the way I wanted to after your code snippet is executed?
@tronje Yes exactly. For example, split_first_mut() returns a mutable reference to the first element in the vector (the element stay in the vector) and a mutable slice (which can also be called "view"). We are only referencing the original data.
2

You're right, you can't borrow an object both immutably and mutably at the same time; in order to get this working you can do the following:

let new_value = my_vec[1].value.clone(); my_vec[0].change_value(&new_value); 

When you clone() the value from myvec[1], you are no longer borrowing myvec and are free to use the new_value in the next line.

3 Comments

Thanks for your answer! Is that a common thing to do in Rust? It seems a bit awkward, especially if my .value wasn't a String, but a larger, more complex object, but I guess there isn't really anything else one can do...
@tronje Actually, cloning should be avoided. Of course, in some some code performance doesn't matter at all, but generally cloning to satisfy the borrow checker should be avoided. Although, sometimes it's the easiest solution.
@tronje This is just an easy solution for this simple case; it should be avoided for big objects.
0

Since this question appears on Google, but doesn't include a generic solution, I'm gonna add one. Along with an API update.

As of 2024, there is the unstable slice.get_many_mut() (tracking issue #104642), which accepts takes a [usize; N] and returns Result<[&mut T; N], _>:

#![feature(get_many_mut)] fn main() { let mut arr = [0, 1, 2, 3, 4, 5]; println!("{:?}", arr.get_many_mut([0, 1])); // It retains the order of the indices println!("{:?}", arr.get_many_mut([1, 0])); // You can do as many indices as you want: println!("{:?}", arr.get_many_mut([5, 0, 2, 3])); } 

Since most people like aren't using Nightly and unstable features, then we'll need to roll our own solution for now (or find a library providing it).

The more general use case is likely to "get two mutable items". If we want to retain the order of the indices, while avoiding unsafe code, then it is definitely a lot easier to implement get_two_mut() than get_many_mut().

So let's implement a trait GetTwoMut. Lukas' answer already mentioned split_at_mut() which we are going to use.

In short, we want to split_at_mut() such that our &mut [T] turns into (&mut [T], &mut [T]) where each of the two slices contain one of the indices. Then we can mutably borrow one item from each slice:

use std::mem; pub trait GetTwoMut<T> { fn get_two_mut(&mut self, indices: [usize; 2]) -> Option<[&mut T; 2]>; } impl<T> GetTwoMut<T> for [T] { fn get_two_mut(&mut self, [mut index0, mut index1]: [usize; 2]) -> Option<[&mut T; 2]> { // Swap so `index1` is the largest index let swap = index0 > index1; if swap { mem::swap(&mut index0, &mut index1); } if index0 == index1 { // We cannot mutably borrow the same index twice return None; } else if self.len() <= index1 { // The largest index is out-of-bounds return None; } // Split the slices into two slice references, such // that `index0` is in `lhs` and `index1` is in `rhs` let (lhs, rhs) = self.split_at_mut(index0 + 1); let mut index1 = &mut rhs[index1 - index0 - 1]; let mut index0 = &mut lhs[index0]; // If the indices were swapped, so swap the items if swap { mem::swap(&mut index0, &mut index1); } Some([index0, index1]) } } 

The only difference between get_two_mut() and get_many_mut() (when using [usize; 2]). Is that get_two_mut() returns an Option, while get_many_mut() returns a Result with a specific GetManyMutError. Otherwise their behavior is the same.

If unsafe code is allowed, then the implementation can be simplified significantly:

impl<T> GetTwoMut<T> for [T] { fn get_two_mut(&mut self, [index0, index1]: [usize; 2]) -> Option<[&mut T; 2]> { if index0 == index1 { return None; } else if self.len() <= index0.max(index1) { return None; } let index0 = unsafe { &mut *(&mut self[index0] as *mut T) }; let index1 = unsafe { &mut *(&mut self[index1] as *mut T) }; Some([index0, index1]) } } 

Additionally, this can easily be reworked into a custom get_many_mut() if needed. However, I will leave that as an exercise to the reader if needed.

I do want to emphasis though, that you should NOT use the unsafe version without reason. If you're implementing a custom get_many_mut(), then feel to use it as a baseline. But verify the safety beyond the 1 minute I spend on writing it.


Here's a test case, that asserts that both get_two_mut() and get_many_mut() produces the same items and order:

#[test] fn test_get_two_mut_vs_get_many_mut() { let mut arr = [0, 1, 2, 3, 4, 5]; let cases = [ // Valid: [0, 1], [1, 0], [1, 3], [3, 1], [2, 3], [3, 2], [2, 4], [4, 2], [0, 5], [5, 0], // Same index: [0, 0], [2, 2], // Out of bounds: [0, 10], [10, 10], [0, 1000], [1000, 0], ]; for indices in cases { let get_two_mut = arr.get_two_mut(indices).map(|[a, b]| [*a, *b]); let get_many_mut = arr.get_many_mut(indices).map(|[a, b]| [*a, *b]).ok(); assert_eq!(get_two_mut, get_many_mut); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.