如何获得多个可变引用元素在Vec?



我有一个大的嵌套数据结构,并且想要抽出几个部分来传递以进行处理。最终,我想将部分发送到多个线程进行更新,但我想先了解一下下面演示的简单示例。在C语言中,我将组装一个相关指针的数组。这在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
}
}

游乐场


我相信我学到的是Rust编译器在这种情况下需要一个迭代器,因为这是唯一的方法,它可以知道每个mut切片来自不同的向量。

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

如果在for循环中创建了另一个mut_slices_iter(可以抓取两次相同的数据),编译器会抱怨吗?

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

我是否正确,.nth方法将调用next()n次,所以它最终是(n)在第一个轴上?

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

如果您想要唯一索引,这实际上更有意义,因为您不能/不应该对同一个元素有两个可变引用。您可以使用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()
}

游乐场

您仍然可以使用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()
}

最新更新