fn change(a: &mut i32, b: &mut i32) {
let c = *a;
*a = *b;
*b = c;
}
fn main() {
let mut v = vec![1, 2, 3];
change(&mut v[0], &mut v[1]);
}
当我编译上面的代码时,它有错误:
error[E0499]: cannot borrow `v` as mutable more than once at a time
--> src/main.rs:9:32
|
9 | change(&mut v[0], &mut v[1]);
| - ^ - first borrow ends here
| | |
| | second mutable borrow occurs here
| first mutable borrow occurs here
为什么编译器禁止它? v[0]和v[1]占据不同的内存位置,因此一起使用它们并不危险。 如果遇到这个问题该怎么办?
您可以使用split_at_mut()解决此问题:
let mut v = vec![1, 2, 3];
let (a, b) = v.split_at_mut(1); // Returns (&mut [1], &mut [2, 3])
change(&mut a[0], &mut b[0]);
不幸的是,有许多安全的事情要做,编译器尚未意识到。 split_at_mut()就像这样,是在内部使用unsafe块实现的安全抽象。
对于这个问题,我们也可以这样做。以下是我在代码中使用的一些东西,无论如何我都需要将这三种情况分开(I:索引超出范围,II:指数相等,III:单独索引)。
enum Pair< T > {
Both(T, T),
One(T),
None,
}
fn index_twice< T >(slc: &mut [T], a: usize, b: usize) -> Pair {
if a == b {
slc.get_mut(a).map_or(Pair::None, Pair::One)
} else {
if a >= slc.len() || b >= slc.len() {
Pair::None
} else {
// safe because a, b are in bounds and distinct
unsafe {
let ar = &mut *(slc.get_unchecked_mut(a) as *mut _);
let br = &mut *(slc.get_unchecked_mut(b) as *mut _);
Pair::Both(ar, br)
}
}
}
}
在夜间通道上,可以使用切片进行模式匹配。只要您没有巨大的索引并且在编译时就知道索引,就可以使用它。
#![feature(slice_patterns)]
fn change(a: &mut i32, b: &mut i32) {
let c = *a;
*a = *b;
*b = c;
}
fn main() {
let mut arr = [5, 6, 7, 8];
{
let &mut [ref mut a, _, ref mut b, _..] = &mut arr;
change(a, b);
}
assert_eq!(arr, [7, 6, 5, 8]);
}
请注意,您需要启用功能slice_patterns。
Rust的借用规则需要在编译时检查,这就是为什么像可变地借用Vec的一部分之类的问题很难解决(即使不是不可能)的问题,以及为什么Rust无法实现。
因此,当您执行&mut v[i]之类的操作时,它将可变地借用整个向量。
想象我做了类似的事情
let guard = something(&mut v[i]);
do_something_else(&mut v[j]);
guard.do_job();
在这里,我创建了一个对象guard,该对象在内部存储了对v[i]的可变引用,并在调用do_job()时对其进行了处理。
同时,我做了一些更改v[j]的操作。 guard拥有一个可变引用,该引用应保证没有其他内容可以修改v[i]。在这种情况下,只要i与j不同,一切都很好。如果两个值相等,则将严重违反借用规则。
由于编译器不能保证i != j,因此被禁止。
这是一个简单的示例,但是类似的情况也很常见,这就是为什么这种访问方式会可变地借用整个容器的原因。加上编译器实际上对Vec的内部知识了解不足以确保即使i != j,此操作也是安全的这一事实。
在确切的情况下,您可以查看Vec上可用的swap(..)方法,该方法执行您正在手动实现的交换。
在更一般的情况下,您可能需要另一个容器。可能会将Vec的所有值包装为具有内部可变性的类型,例如Cell或RefCell,甚至使用完全不同的容器,如@llogiq在他对par-vec的回答中建议的那样。
方法[T]::iter_mut()返回一个迭代器,该迭代器可以为切片中的每个元素生成可变的引用。其他集合也具有iter_mut方法。这些方法通常封装不安全的代码,但是它们的接口是完全安全的。
这是一个通用扩展特性,它在切片上添加一个方法,该方法按索引返回对两个不同项目的可变引用:
pub trait SliceExt {
type Item;
fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item);
}
impl< T > SliceExt for [T] {
type Item = T;
fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item) {
match index0.cmp(&index1) {
Ordering::Less => {
let mut iter = self.iter_mut();
let item0 = iter.nth(index0).unwrap();
let item1 = iter.nth(index1 - index0 - 1).unwrap();
(item0, item1)
}
Ordering::Equal => panic!("[T]::get_two_mut(): received same index twice ({})", index0),
Ordering::Greater => {
let mut iter = self.iter_mut();
let item1 = iter.nth(index1).unwrap();
let item0 = iter.nth(index0 - index1 - 1).unwrap();
(item0, item1)
}
}
}
}
您不能对同一数据进行两个可变引用。这是借阅检查器明确禁止的操作,以防止同时进行修改。但是,您可以使用unsafe块绕过借用检查器。
虽然在您的情况下v[0]和v[1]显然是分开的大块,但这并没有受到严格的审查。如果v是某种称为NullMap的映射,该映射将所有元素映射到单个字段怎么办?编译器如何知道Vec操作v[0];v[1];是安全的,而NullMap不是?
如果要交换数组的两个元素,为什么不使用slice::swap?
fn main() {
let mut v = vec![1, 2, 3];
v.swap(0,1);
println!("{:?}",v);
}
另外,v也必须为mut,因为要更改矢量。一个不可变的版本将克隆并对其执行交换。
问题在于&mut v[…]首先可变地借用v,然后将可变引用该元素提供给change-function。
此reddit评论可以解决您的问题。
编辑:感谢您的单挑,Shepmaster。 par-vec是一个库,可以可变地借用vec的分离分区。
请内联解决方案。 随着时间的推移,链接会过时。