rust 如何同时获得对两个数组元素的可变引用?

dsf9zpds  于 2022-12-19  发布在  其他
关注(0)|答案(9)|浏览(172)
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]占用不同的内存位置,所以一起使用并不危险。如果我遇到这个问题该怎么办?

rqdpfwrv

rqdpfwrv1#

你可以用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)
            }
        }
    }
}
5us2dqdw

5us2dqdw2#

从Rust1.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]);
}
qmelpv7a

qmelpv7a3#

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的所有值 Package 成一个内部可变的类型,比如CellRefCell,或者甚至使用一个完全不同的容器,就像@llogiq在他对par-vec的回答中建议的那样。

new9mtju

new9mtju4#

[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)
            }
        }
    }
}
a1o7rhls

a1o7rhls5#

在最近的夜生活中,有get_many_mut()

#![feature(get_many_mut)]

fn main() {
    let mut v = vec![1, 2, 3];
    let [a, b] = v
        .get_many_mut([0, 1])
        .expect("out of bounds or overlapping indices");
    change(a, b);
}
kqlmhetl

kqlmhetl6#

你不能对同一个数据做两个可变的引用。这是借用检查器明确禁止的,以防止并发修改。但是你可以使用unsafe块绕过借用检查器。
虽然在您的例子中v[0]v[1]是明显分开的块,但这并不经得起认真的审查。如果v是某种称为NullMap的Map,它将所有元素Map到单个字段呢?编译器如何知道在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,因为你要改变向量,一个不变的版本会克隆它并在它上面执行交换。

z9ju0rcb

z9ju0rcb7#

在@bluss给出的答案的基础上,我们可以使用split_at_mut()创建一个函数,将向量的可变借位转换为向量元素的可变借位的向量:

fn borrow_mut_elementwise<'a, T>(v:&'a mut Vec<T>) -> Vec<&'a mut T> {
        let mut result:Vec<&mut T> = Vec::new();
        let mut current: &mut [T];
        let mut rest = &mut v[..];
        while rest.len() > 0 {
            (current, rest) = rest.split_at_mut(1);
            result.push(&mut current[0]);
        }
        result
    }

然后,你可以用它来获得一个绑定,让你一次改变原始Vec的许多项,即使你正在迭代它们(如果你在循环中通过索引访问它们,而不是通过任何迭代器):

let mut items = vec![1,2,3];
    let mut items_mut = borrow_mut_elementwise(&mut items);
    for i in 1..items_mut.len() {
        *items_mut[i-1] = *items_mut[i];
    }
    println!("{:?}", items); // [2, 3, 3]
qmb5sa22

qmb5sa228#

问题是&mut v[…]首先可变地借用v,然后将对元素的可变引用提供给change函数。
This reddit评论有一个解决你的问题的方法。
编辑:谢谢你的提醒,Shepmaster. par-vec是一个库,它允许可变地借用vec的分离分区。

bzzcjhmw

bzzcjhmw9#

我将我的每日效用发布到crate.io.Link to the doc
你可以这样使用它

use arref::array_mut_ref;
let mut arr = vec![1, 2, 3, 4];
let (a, b) = array_mut_ref!(&mut arr, [1, 2]);
assert_eq!(*a, 2);
assert_eq!(*b, 3);
let (a, b, c) = array_mut_ref!(&mut arr, [1, 2, 0]);
assert_eq!(*c, 1);

// ⚠️ The following code will panic. Because we borrow the same element twice.
// let (a, b) = array_mut_ref!(&mut arr, [1, 1]);

这是对下面代码的简单 Package ,很合理,但它要求两个索引在运行时不同。

pub fn array_mut_ref<T>(arr: &mut [T], a0: usize, a1: usize) -> (&mut T, &mut T) {
    assert!(a0 != a1);
    // SAFETY: this is safe because we know a0 != a1
    unsafe {
        (
            &mut *(&mut arr[a0] as *mut _),
            &mut *(&mut arr[a1] as *mut _),
        )
    }
}

或者,您可以使用不会对mut_twice造成混乱的方法

#[inline]
pub fn mut_twice<T>(arr: &mut [T], a0: usize, a1: usize) -> Result<(&mut T, &mut T), &mut T> {
    if a0 == a1 {
        Err(&mut arr[a0])
    } else {
        unsafe {
            Ok((
                &mut *(&mut arr[a0] as *mut _),
                &mut *(&mut arr[a1] as *mut _),
            ))
        }
    }
}

相关问题