How to create two reference to different elements of a Vec when at least one is mutable

This isn’t the first time that I difficulties understanding how I should re-write my code to please the borrow checker when I try to take two references (and at least one of them being mutable) to different elements of the same vector (or any other container for that matter).

Basically, what I’m trying to do is to access mutably to an element of a vector, based on the value of another element of the same vector, where the index of both elements aren’t the same (otherwise the code must panic).

let mut elements = vec![2, 1, 1];
let v1 = &elements[0]; // take a reference to the element at index 0
assert!(v1 != 0);
let v2 = &mut elements[*v1]; // this doesn’t compile
/* ... (modify v2) */

Not that the element are not Copy in my real code, hence why I need to take a reference for v1.


I don’t understand why there isn’t some kind of punch_hole(container, index) function in std that would return a mutable reference to the element at the index in the container alongside a mutable "view" of the container where we can access all the element but the one at index (ie. trying to access the element at the index of the hole would panic).

I did an experimental implementation on the [Rust Playground) for Vec<T>. A similar logic could be used to implement it for any other containers.

This can be done with split_at_mut.

2 Likes

More concretely, something like

fn foo(elements: &mut [usize], i: usize) {
    let (elements_left, v1_and_elements_right) = elements.split_at_mut(i);
    let (v1, elements_right) = v1_and_elements_right.split_first_mut().unwrap();
    // The types are:
    // v1: &mut usize
    // elements_left, elements_right: &mut [usize]

    let v2 = match *v1 {
        j if j < i => &mut elements_left[j],
        j if j > i => &mut elements_right[j - (i + 1)],
        _ => panic!("must not point to itself"),
    };
    // ....
}

works. It isn’t super pretty, but it works.

For your special case of i == 0, you’d—of course—only need to call split_first_mut.

fn bar(elements: &mut [usize]) {
    let (v1, elements_right) = elements.split_first_mut().unwrap();
    // The types are:
    // v1: &mut usize
    // elements_right: &mut [usize]
    assert!(*v1 != 0, "must not point to itself");
    let v2 = &mut elements_right[*v1 - 1];
    // ....
}
2 Likes

Using split_at_mut() works "well" only when there is a single hole (the index of v1 in my example). As soon as you want to access to v2 whose index depends on v1 (the second hole) whose index depends of an initial index (the first hole), it becomes extremely un-ergonomic (or you must pass both the left an right slices). This also only works for Vec, but not for something like HashMap.

1 Like

I recently contributed a couple of methods on hashbrown::HashMap that enable safely getting mutable references to several values at once, taking advantage of min_const_generics:

https://rust-lang.github.io/hashbrown/hashbrown/struct.HashMap.html#method.get_each_mut

3 Likes

It's not particularly efficient, but you can also do something like this:

let vec: Vec<T> = vec![ ... ];
let mut vec_refs: Vec<Option<&mut T>> = vec.iter_mut().map(Some).collect();

let a_ref: &mut T = vec_refs[a].take().unwrap();
let b_ref: &mut T = vec_refs[b].take().unwrap();
/* etc... */

Edit: Similarly for a HashMap:

let mut map: HashMap<K,V> = ...;
let mut ref_map: HashMap<&K, &mut V> = map.iter_mut().collect();

Edit 2: Can you extract the necessary index and then drop the reference?

let mut elements = vec![2, 1, 1];
let idx:usize = get_next_index(&elements[0]);
let v2 = &mut elements[idx];

In some cases it is possible to use Cell to do it:

https://ryhl.io/blog/temporary-shared-mutation/

4 Likes

@alice :heart: You article is exactly what I needed to read!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.