如何在 Rust 中编写自己的交换函数?

hkB*_*Bst 1 swap rust

std::mem::swap有签名:

pub fn swap<T>(x: &mut T, y: &mut T)
Run Code Online (Sandbox Code Playgroud)

如果我尝试实现它(游乐场):

pub fn swap<T>(x: &mut T, y: &mut T)
Run Code Online (Sandbox Code Playgroud)

我收到有关两个参数的生命周期的错误:

pub fn swap<T>(a: &mut T, b: &mut T) {
    let t = a;
    a = b;
    b = t;
}
Run Code Online (Sandbox Code Playgroud)

如果我将签名更改为:

error[E0623]: lifetime mismatch
 --> src/lib.rs:4:9
  |
1 | pub fn swap<T>(a: &mut T, b: &mut T) {
  |                   ------     ------
  |                   |
  |                   these two types are declared with different lifetimes...
...
4 |     b = t;
  |         ^ ...but data from `a` flows into `b` here

error[E0623]: lifetime mismatch
 --> src/lib.rs:3:9
  |
1 | pub fn swap<T>(a: &mut T, b: &mut T) {
  |                   ------     ------ these two types are declared with different lifetimes...
2 |     let t = a;
3 |     a = b;
  |         ^ ...but data from `b` flows into `a` here
Run Code Online (Sandbox Code Playgroud)

它可以编译,但我收到一条警告,这似乎意味着我们只是交换临时副本:

pub fn swap_lt<'t, T>(mut a: &'t T, mut b: &'t T)
Run Code Online (Sandbox Code Playgroud)

Sve*_*ach 6

您的代码不在临时副本上运行。它只是交换传入的引用,这对它们指向的值没有任何影响。这也解释了为什么编译器希望生命周期匹配 \xe2\x80\x93 引用指向之前指向的x值引用y,反之亦然,这只有在两个引用具有相同生命周期的情况下才有可能。

\n

当交换实际值时,会出现不同的问题。您首先需要将其中一个值移至临时变量。但是,由于Tis not Copy,因此您无法将值从引用后面移出,因为这会使引用无效,而这在 Rust 中是不允许的。如果您允许T: Default,您可以暂时将该值替换为其默认值。但是,如果要实现一般情况下的功能,则需要诉诸不安全代码。一种方法是使用std::ptr::read()std::ptr::write()函数从原始指针读取和写入数据:

\n
fn swap<T>(x: &mut T, y: &mut T) {\n    unsafe {\n        let z = read(x);\n        write(x, read(y));\n        write(y, z);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这段代码比看起来更棘手。该read()函数返回值的副本,而不会使原始值无效,因此我们最终会Copy在两个地方出现相同的非值。我们需要注意不要删除任何值,这在许多情况下会隐式发生。例如,这个实现是错误的,因为它隐式地删除了x最初指向的值

\n
fn swap<T>(x: &mut T, y: &mut T) {\n    unsafe {\n        let z = read(x);\n        *x = read(y);     // Wrong \xe2\x80\x93 drops the original value x is pointing to\n        write(y, z);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

标准库中的实际实现swap()使用了一些优化:

\n
    \n
  • 它使用std::ptr::copy_nonoverlapping()函数 而不是write(x, read(y)),后者作为编译器内部函数实现。Rust 编译器将其委托给 LLVM,以确保生成的代码对于目标平台尽可能高效。我们的代码实际上使用临时存储来存储 。使用,仅需要临时存储其中一个变量。xycopy_nonoverlapping()

    \n
  • \n
  • 大小为 32 或更大的值按块交换,因此只需要 32 字节的临时存储。

    \n
  • \n
\n