如何交换数组、切片或 Vec 的元素?

29

我想使用库函数交换片段data的元素,但由于多个借用而无法工作:

use std::mem;

fn example() {
    let mut data = [1, 2, 3];
    let i = 0;
    let j = 1;
    
    mem::swap(&mut data[i], &mut data[j]);
}

error[E0499]: cannot borrow `data[_]` as mutable more than once at a time
 --> src/lib.rs:8:29
  |
8 |     mem::swap(&mut data[i], &mut data[j]);
  |     --------- ------------  ^^^^^^^^^^^^ second mutable borrow occurs here
  |     |         |
  |     |         first mutable borrow occurs here
  |     first borrow later used by call
  |

这件事可以手动完成,但我认为每次都使用这段代码并不是最好的方式:

let temp = data[i];
data[i] = data[j];
data[j] = temp;

有没有其他方法可以交换切片中的元素?


1
“通常情况下,可以手动完成”,但在复制类型上,则会导致“无法移动出索引内容”的结果。 - Andrey Tyukin
2个回答

43

切片上有一个 swap 方法data.swap(i, j)

原代码无法工作,因为该语言要求 &mut 不具有别名,也就是说,如果通过 &mut 访问数据,则必须没有其他使用该数据的方式。通常情况下,对于连续的索引 data[i]data[j],编译器无法保证 ij 是不同的。如果它们相同,则索引引用的是同一内存,因此 &mut data[i]&mut data[j] 将是指向相同数据的两个指针:非法!

.swap 在内部使用了一些 unsafe 代码,确保正确处理 i == j 的情况,避免别名 &mut 指针。尽管如此,它并不一定要使用 unsafe,只是为了确保这个“原始”操作具有高性能(我可以想象未来的语言/库改进会通过使所需的不变量更容易表达来减少这里的不安全需求),例如以下是一个安全的实现:

use std::cmp::Ordering;
use std::mem;

fn swap<T>(x: &mut [T], i: usize, j: usize) {
    let (lo, hi) = match i.cmp(&j) {
        // no swapping necessary
        Ordering::Equal => return,

        // get the smallest and largest of the two indices
        Ordering::Less => (i, j),
        Ordering::Greater => (j, i),
    };

    let (init, tail) = x.split_at_mut(hi);
    mem::swap(&mut init[lo], &mut tail[0]);
}

关键在于split_at_mut,它将切片分成两个不相交的部分(这是通过unsafe内部实现的,但Rust标准库建立在unsafe上:语言提供“原始”功能,库在其基础之上构建剩余的内容)。


在ptr模块中存在:https://github.com/rust-lang/rust/blob/master/src/libcore/ptr.rs#L164 - swizard
它也可以使用临时变量来实现,我不明白为什么需要这样复杂的实现。双重借用的问题源于函数调用语法swap(mut & x, mut & y),当x、y指向同一数据片段时。如果函数语法是swap(& mut[], i, j),则在其中使用临时变量进行交换就没有问题了。 - Arsenii Fomin
2
@FominArseniy,如果类型是Copy,那么它只能使用临时变量。如果包含的类型移动了(例如data: &mut [String]),则无法使用您在问题中提供的代码,而我提供的谨慎代码可以正常工作。 - huon
我认为索引&mut tail[hi]是错误的,应该是&mut tail[0] - Matthieu M.
1
@FominArseniy,是的,这是指针版本,通过以下方式实现了切片过程:https://github.com/rust-lang/rust/blob/master/src/libcore/slice.rs#L352 - swizard
显示剩余3条评论

0
可以使用元组解构来交换数组中的两个或多个元素。
// Initial data
let mut data = [1, 2, 3, 4, 5, 6];

// Swap three elements around
(data[1], data[3], data[5]) = (data[3], data[5], data[1]);

// End result
assert_eq!(data, [1, 4, 3, 6, 5, 2]);

1
只有当元素是“Copy”时,这才有效,并且应该比“swap()”性能差。 - Chayim Friedman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接