如何获取对 Vec 中元素的多个可变引用?

How to get multiple mutable references to elements in a Vec?

我有一个很大的嵌套数据结构,想挑出一些部分传递给处理。最终,我想将部分发送到多个线程进行更新,但我想深入了解我在下面说明的简单示例。在 C 中,我只是 assemble 一个相关指针的数组。这在 Rust 中似乎可行,因为内部向量永远不需要多个可变引用。这是示例代码。

fn main() {
    let mut data = Data::new(vec![2, 3, 4]);
    // this works
    let slice = data.get_mut_slice(1);
    slice[2] = 5.0;
    println!("{:?}", data);

    // what I would like to do
    // let slices = data.get_mut_slices(vec![0, 1]);
    // slices[0][0] = 2.0;
    // slices[1][0] = 3.0;
    // println!("{:?}", data);
}

#[derive(Debug)]
struct Data {
    data: Vec<Vec<f64>>,
}

impl Data {
    fn new(lengths: Vec<usize>) -> Data {
        Data {
            data: lengths.iter().map(|n| vec![0_f64; *n]).collect(),
        }
    }

    fn get_mut_slice(&mut self, index: usize) -> &mut [f64] {
        &mut self.data[index][..]
    }

    // doesnt work
    // fn get_mut_slices(&mut self, indexes: Vec<usize>) -> Vec<&mut [f64]> {
    //     indexes.iter().map(|i| self.get_mut_slice(*i)).collect()
    // }
}

只要您非常小心,使用安全的 Rust 是可能的。诀窍是利用 Vec 上安全 .iter_mut().nth() 方法背后的标准库中的不安全 Rust 代码。这是一个工作示例,其中包含在上下文中解释代码的注释:

fn main() {
    let mut data = Data::new(vec![2, 3, 4]);

    // this works
    let slice = data.get_mut_slice(1);
    slice[2] = 5.0;
    println!("{:?}", data);

    // and now this works too!
    let mut slices = data.get_mut_slices(vec![0, 1]);
    slices[0][0] = 2.0;
    slices[1][0] = 3.0;
    println!("{:?}", data);
}

#[derive(Debug)]
struct Data {
    data: Vec<Vec<f64>>,
}

impl Data {
    fn new(lengths: Vec<usize>) -> Data {
        Data {
            data: lengths.iter().map(|n| vec![0_f64; *n]).collect(),
        }
    }

    fn get_mut_slice(&mut self, index: usize) -> &mut [f64] {
        &mut self.data[index][..]
    }

    // now works!
    fn get_mut_slices(&mut self, mut indexes: Vec<usize>) -> Vec<&mut [f64]> {
        // sort indexes for easier processing
        indexes.sort();
        let index_len = indexes.len();

        // early return for edge case
        if index_len == 0 {
            return Vec::new();
        }

        // check that the largest index is in bounds
        let max_index = indexes[index_len - 1];
        if max_index > self.data.len() {
            panic!("{} index is out of bounds of data", max_index);
        }

        // check that we have no overlapping indexes
        indexes.dedup();
        let uniq_index_len = indexes.len();
        if index_len != uniq_index_len {
            panic!("cannot return aliased mut refs to overlapping indexes");
        }

        // leverage the unsafe code that's written in the standard library
        // to safely get multiple unique disjoint mutable references
        // out of the Vec
        let mut mut_slices_iter = self.data.iter_mut();
        let mut mut_slices = Vec::with_capacity(index_len);
        let mut last_index = 0;
        for curr_index in indexes {
            mut_slices.push(
                mut_slices_iter
                    .nth(curr_index - last_index)
                    .unwrap()
                    .as_mut_slice(),
            );
            last_index = curr_index;
        }

        // return results
        mut_slices
    }
}

playground


What I believe I learned is that the Rust compiler demands an iterator in this situation because that is the only way it can know that each mut slice comes from a different vector.

编译器实际上并不知道。它只知道迭代器 returns mut 引用。底层实现使用不安全的 Rust,但方法 iter_mut() 本身是安全的,因为实现保证只发出每个 mut ref 一次,并且所有 mut ref 都是唯一的。

Would the compiler complain if another mut_slices_iter was created in the for loop (which could grab the same data twice)?

是的。在 Vec 上调用 iter_mut() 可变地借用它并且重叠可变借用相同的数据违反了 Rust 的所有权规则,所以你不能在同一范围内调用 iter_mut() 两次(除非迭代器返回由第一个调用在第二个调用之前挂断)。

Also am I right that the .nth method will call next() n times so it is ultimately theta(n) on the first axis?

不完全是。这是 nth 的默认实现但是通过在 Vec 上调用 iter_mut() 返回的迭代器使用 its own custom implementation 并且它似乎跳过迭代器中过去的项目而不调用 next()所以它应该和你只是定期索引到 Vec 一样快,即使用 .nth() 获得 3 个随机索引的项目在 10000 个项目的迭代器上和在迭代器上一样快10 个项目,尽管这仅适用于从支持快速随机访问的集合创建的迭代器,如 Vecs.

如果你想要唯一索引,这实际上更有意义,因为你不能't/shoudn对同一元素有两个可变引用。您可以使用 HashSet 而不是 Vec 并使用一些迭代器组合:

    fn get_mut_slices(&mut self, indexes: HashSet<usize>) -> Vec<&mut [f64]> {
        self.data
            .iter_mut()
            .enumerate()
            .filter(|(i, _)| indexes.contains(i))
            .map(|(_, e)| e.as_mut_slice())
            .collect()
    }

Playground

您仍然可以使用 Vec 作为此选项,但使用 contains 时效率会低得多:

    fn get_mut_slices(&mut self, indexes: Vec<usize>) -> Vec<&mut [f64]> {
        self.data
            .iter_mut()
            .enumerate()
            .filter(|(i, _)| indexes.contains(i))
            .map(|(_, e)| e.as_mut_slice())
            .collect()
    }