考虑这个简单的例子:v: Vec<Vec<i32>>
我想添加v[1]
to v[0]
.
我什至没有考虑牺牲性能,因此克隆任何载体都不是一种选择。因此,无论我们如何精确地实现向量的加法,我们都需要内部同时引用v
: &mut v[0]
and &v[1]
。显然这里的问题是索引借用了v
和可变上下文中的索引借用v
可变的,所以借用检查器不会允许这样做。
这个例子导致了一个更普遍的问题:我们如何保持同时引用不同的如果所有返回(可变)元素引用的方法(可变地)借用容器本身,那么容器的元素会怎样?
请注意,我确实理解问题的根源以及为什么在编译时借用检查器无法看到我们正在引用不同的元素。问题是我们如何告诉编译器我们正在做正确的事情而不牺牲性能和/或安全性?
目前我知道 3 种可能的解决方案,并且不会产生明显的性能开销:
-
slice::split_at_mut
是一个不错的解决方法,但不幸的是仅适用于顺序/切片式容器。请注意,该函数在实现中使用了不安全的代码。
- 使用迭代器:是的,我们可以保存从迭代器返回的同时引用,但在许多情况下使用迭代器是不正确的,例如,
map: HashMap<i32, Vec<i32>>
我想再次补充map[1]
to map[0]
。请注意(据我所知)迭代器也使用不安全代码(直接或间接)。
- 最后是适用于每个容器的解决方案:
RefCell<T>
,即将容器的元素包装在RefCell
。 (嗯,或者Cell<T>
在某些情况下)。但这有两个问题。首先是运行时借用检查的轻微性能开销。第二个是,例如,如果我正在编写一个返回容器的函数,我要么必须让调用者使用我的RefCell
- 包装容器或复制整个容器以删除RefCell
包装(有没有办法打开RefCell
免费放在容器内?)再一次,RefCell
使用不安全的代码。
所有这些解决方案都使用不安全的代码。这真的是唯一的方法吗?我也很确定有一个直接使用不安全代码的解决方案,但作为初学者,我有点害怕深入研究不安全的 Rust。不过,如果它恰好是一个好的解决方案,请指出我需要研究的主题。
还有其他解决方案吗?哪一种更实用?有不懂的地方或者理解错误的地方请大家指正。
Edit:正如斯文·马尔纳克(Sven Marnach)指出的那样,我的问题太宽泛,所以我正在缩小问题范围。
我有一个map: HashMap<i32, Vec<i32>>
我想分配map[0] + map[1]
(逐元素加法)到map[0]
性能开销为零。在这里,matiu 建议的重复索引并不是最佳选择,因为它会涉及对同一键的多次搜索。那么,有可能吗?如果不是,这种情况的最佳解决方案是什么?
所以目标是用 vec[0] + vec[1] 覆盖 vec[0] ?
我认为诀窍是使用 Vec 的索引,而不是保持引用打开。
这达到目标了吗?
fn main() {
let mut vec = vec![
vec![1, 2, 3],
vec![10, 20, 30],
];
let ln = vec[0].len();
for i in 0..ln {
vec[0][i] += vec[1][i];
}
println!("{:?}", vec);
}
我还想测试就地执行是否实际上比创建新数组更快。 “也许编译器足够聪明,可以重用内部内存”我想。事实证明编译器并不那么聪明。
带索引的 for 循环是最快的方法。
Code:
结果(在我的笔记本电脑上):
running 7 tests
test bench_create_new_array ... bench: 230 ns/iter (+/- 0)
test bench_for_indexes ... bench: 174 ns/iter (+/- 0)
test bench_new_array_borrow ... bench: 231 ns/iter (+/- 0)
test bench_to_owned1 ... bench: 1,097 ns/iter (+/- 4)
test bench_to_owned_in_place ... bench: 240 ns/iter (+/- 1)
test bench_to_owned_in_place2 ... bench: 1,080 ns/iter (+/- 159)
test bench_to_owned_in_place3 ... bench: 1,037 ns/iter (+/- 2)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)