Multiple mutable references from vector content... how?

Hi everybody.

This seems to be an evergreen problem to Rust newbies, but I can't find a solved help request similar enough to my problem.

fn main() {

    let mut buffers = vec![
        vec![0.0; 128],
        vec![0.0; 128],
        vec![0.0; 128],
        vec![0.0; 128]
    ];

    let buffer_order: Vec<usize> = vec![2, 0, 1];

    let mut references: Vec<&mut [f32]> = Vec::new();

    for idx in buffer_order {
        references.push(buffers[idx].as_mut_slice());
    }

}

What I'm trying to do is build a vector of mutable references to some slices of vectors contained in a collection of vectors, buffers.

I get why the compiler is yelling at me: the sequence of idx values are known at runtime, if for any reason I get the same index twice I'd end up with two mutable references to the same thing.

But: I know, based on how it is built, that the indexes contained in buffer_order are unique; if not, getting a panic is fine.

How can I achieve this?
Note: performance here is important. Also, an unsafe solution could be interesting!

Thanks!

You know but the compiler doesn't. What you are trying to achieve is some sort of split borrow which doesn't work on collections:

However borrowck doesn't understand arrays or slices in any way, so this doesn't work:

let mut x = [1, 2, 3];
let a = &mut x[0];
let b = &mut x[1];
println!("{} {}", a, b);

If it is okay for you to panic you could try interior mutability by wrapping each buffer in a RefCell. This won't work if you need a contiguous memory layout though. When it comes to unsafe solutions someone with more knowledge about that must help you.

This is a safe solution, but it would be inefficient if buffers is a lot longer than buffer_order.

fn main() {
    let mut buffers = vec![
        vec![0.0; 128],
        vec![0.0; 128],
        vec![0.0; 128],
        vec![0.0; 128],
    ];

    let buffer_order: Vec<usize> = vec![2, 0, 1];

    let mut references_ordered: Vec<Option<&mut [f32]>> =
        buffers.iter_mut().map(|x| Some(x.as_mut_slice())).collect();

    let mut references: Vec<&mut [f32]> = Vec::with_capacity(buffer_order.len());

    for idx in buffer_order {
        references.push(references_ordered[idx].take().unwrap());
    }
}

Here's one safe way, though it involves an intermediate allocation.

(Similar to @steffahn's but without the Options.)

1 Like

In which cases something like this should be faster

fn main() {
    let mut buffers = vec![vec![0.0; 1], vec![1.0; 1], vec![2.0; 1], vec![3.0; 1]];

    let buffer_order: Vec<usize> = vec![2, 0, 1];

    struct Element<'a> {
        source_index: usize,
        original_position: usize,
        reference: &'a mut [f32],
    }
    let mut elements: Vec<Element<'_>> = buffer_order
        .iter()
        .enumerate()
        .map(|(original_position, &source_index)| Element {
            source_index,
            original_position,
            reference: &mut [],
        })
        .collect();
    elements.sort_unstable_by_key(|e| e.source_index);
    let mut slice = &mut buffers[..];
    let mut after_last_pos = 0;
    elements.iter_mut().for_each(|e| {
        assert!(e.source_index >= after_last_pos, "duplicate index");
        (e.reference, slice) = std::mem::take(&mut slice)[e.source_index - after_last_pos..]
            .split_first_mut()
            .expect("out of bounds");
        after_last_pos = e.source_index + 1;
    });
    elements.sort_unstable_by_key(|e| e.original_position);

    let references: Vec<&mut [f32]> = elements.into_iter().map(|e| e.reference).collect();

    dbg!(references);
}

Edit: Slight alteration, using a slice iterator again, instead of manual slicing (because nth should be sufficiently efficient, and it’s nicer to use) and using an earlier constructed references vec and references-to-references to avoid the need for a second round of sorting.

fn main() {
    let mut buffers = vec![vec![0.0; 1], vec![1.0; 1], vec![2.0; 1], vec![3.0; 1]];

    let buffer_order: Vec<usize> = vec![2, 0, 1];

    let mut references: Vec<&mut [f32]> = std::iter::repeat_with(|| &mut [][..])
        .take(buffer_order.len())
        .collect();
    let mut elements: Vec<(usize, &mut &mut [f32])> =
        std::iter::zip(buffer_order, &mut references).collect();
    elements.sort_unstable_by_key(|&(index, _)| index);
    let mut buffers_iter = buffers.iter_mut();
    let mut after_prev_index = 0;
    for (index, target) in elements {
        assert!(index >= after_prev_index, "duplicate index");
        *target = buffers_iter
            .nth(index - after_prev_index)
            .expect("out of bounds");
        after_prev_index = index + 1;
    }

    dbg!(references);
}

Thanks a lot, I didn't expect so much suggestions! steffahn's solution is likely the best suited to my case, but jofas' hint to study intertior mutability maybe could simplify my code.

Thanks again!

1 Like

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.