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

use*_*565 28 rust

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]);
}
Run Code Online (Sandbox Code Playgroud)

当我编译上面的代码时,它有错误:

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
Run Code Online (Sandbox Code Playgroud)

为什么编译器禁止它?v[0]并且v[1]占据不同的记忆位置,因此将它们一起使用并不危险.如果我遇到这个问题该怎么办?

blu*_*uss 28

你可以解决这个问题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]); 
Run Code Online (Sandbox Code Playgroud)

遗憾的是,编译器无法识别许多安全的事情.split_at_mut()就像那样,一个unsafe内部使用块实现的安全抽象.

对于这个问题,我们也可以这样做.以下是我在代码中使用的东西,我需要将所有三种情况分开(I:Index out of bounds,II:Indices equal,III:Separate indices).

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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


oli*_*obk 9

在夜间通道上,可以使用切片完成模式匹配.只要您没有巨大的索引并且您的索引在编译时就已知,您就可以使用它.

#![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]);
}
Run Code Online (Sandbox Code Playgroud)

请注意,您需要启用该功能slice_patterns.


Lev*_*ans 8

Rust的借用规则需要在编译时检查,这就是为什么像可变地借用a的一部分Vec是一个非常难以解决的问题(如果不是不可能的话),以及为什么Rust不可能.

因此,当你做类似的事情时&mut v[i],它将可变地借用整个矢量.

想象一下,我做了类似的事情

let guard = something(&mut v[i]);
do_something_else(&mut v[j]);
guard.do_job();
Run Code Online (Sandbox Code Playgroud)

在这里,我创建了一个guard内部存储可变引用的对象v[i],并在调用时对其执行某些操作do_job().

与此同时,我做了一些改变的事情v[j].guard持有一个可变的引用,该引用应该保证其他任何东西都不能修改v[i].在这种情况下,一切都很好,只要i不同于j; 如果这两个值相等,则极大地违反了借用规则.

由于编译器无法保证,i != j因此禁止使用.

这是一个简单的例子,但类似的情况是军团,这就是为什么这种访问可能会错误地借用整个容器.此外,编译器实际上对内部结构的了解不足以Vec确保此操作即使是安全的i != j.


在您的确切情况下,您可以查看您手动实现的交换的可用swap(..)方法Vec.

在更通用的情况下,您可能需要另一个容器.可能性是将你的所有值包装Vec成具有内部可变性的类型,例如Cellor RefCell,或甚至使用完全不同的容器,如@llogiq在他的回答中所建议的那样par-vec.


Cha*_*man 8

每晚有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);
}
Run Code Online (Sandbox Code Playgroud)


Fra*_*gné 6

该方法[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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)