如何同时获取对两个数组元素的可变引用?
How to get mutable references to two array elements at the same time?
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]
占用不同的内存位置,因此将它们一起使用并不危险。遇到这个问题怎么办?
问题是 &mut v[…]
首先可变借用 v
然后将对元素的可变引用提供给更改函数。
This reddit 评论有解决你问题的方法。
编辑:感谢您的提醒,Shepmaster。 par-vec 是一个允许可变地借用 vec 的分离分区的库。
Rust 的借用规则需要在编译时检查,这就是为什么像可变地借用 a 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(..)
method,它执行您手动实施的交换。
在更一般的情况下,您可能需要另一个容器。可能性是将 Vec
的所有值包装到具有内部可变性的类型中,例如 Cell
or RefCell
, or even using a completely different container, as @llogiq suggested in his answer with par-vec
.
你可以用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<&mut T> {
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)
}
}
}
}
您不能对同一数据进行两个可变引用。这是借用检查器明确禁止的,以防止并发修改。但是,您可以使用 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
,因为你正在改变矢量。不可变版本将克隆并对其执行交换。
从 Rust 1.26 开始,可以在切片上进行模式匹配。只要您没有庞大的索引并且您的索引在编译时已知,您就可以使用它。
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 [ref mut a, _, ref mut b, ..] = arr;
change(a, b);
}
assert_eq!(arr, [7, 6, 5, 8]);
}
方法 [T]::iter_mut()
returns 一个迭代器,它可以为切片中的每个元素生成一个可变引用。其他集合也有一个 iter_mut
方法。这些方法往往封装了不安全的代码,但是它们的接口是完全安全的。
这是一个通用的扩展特征,它在切片上添加了一个方法,returns 通过索引对两个不同的项目进行可变引用:
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)
}
}
}
}
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]
占用不同的内存位置,因此将它们一起使用并不危险。遇到这个问题怎么办?
问题是 &mut v[…]
首先可变借用 v
然后将对元素的可变引用提供给更改函数。
This reddit 评论有解决你问题的方法。
编辑:感谢您的提醒,Shepmaster。 par-vec 是一个允许可变地借用 vec 的分离分区的库。
Rust 的借用规则需要在编译时检查,这就是为什么像可变地借用 a 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(..)
method,它执行您手动实施的交换。
在更一般的情况下,您可能需要另一个容器。可能性是将 Vec
的所有值包装到具有内部可变性的类型中,例如 Cell
or RefCell
, or even using a completely different container, as @llogiq suggested in his answer with par-vec
.
你可以用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<&mut T> {
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)
}
}
}
}
您不能对同一数据进行两个可变引用。这是借用检查器明确禁止的,以防止并发修改。但是,您可以使用 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
,因为你正在改变矢量。不可变版本将克隆并对其执行交换。
从 Rust 1.26 开始,可以在切片上进行模式匹配。只要您没有庞大的索引并且您的索引在编译时已知,您就可以使用它。
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 [ref mut a, _, ref mut b, ..] = arr;
change(a, b);
}
assert_eq!(arr, [7, 6, 5, 8]);
}
方法 [T]::iter_mut()
returns 一个迭代器,它可以为切片中的每个元素生成一个可变引用。其他集合也有一个 iter_mut
方法。这些方法往往封装了不安全的代码,但是它们的接口是完全安全的。
这是一个通用的扩展特征,它在切片上添加了一个方法,returns 通过索引对两个不同的项目进行可变引用:
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)
}
}
}
}