在Rust中有循环旋转可变变量的标准方法吗?

时间:2019-06-29 16:30:29

标签: rust swap

实现算法中最常见的操作是循环循环:给定3个变量a,b,c,将它们更改为

t ⇽ c
c ⇽ b
b ⇽ a
a ⇽ t

鉴于所有内容都是按位可交换的,因此循环旋转应该是Rust比我所知道的任何其他语言都擅长的领域。

为了进行比较,在C ++中,旋转N个元素的最有效的通用方法是执行n + 1 std::move个操作,这反过来会粗略地导致(对于典型的移动构造函数实现)3 (n+1) sizeof(T)个单词分配(对于POD,可以通过专门旋转模板来对此进行改进,但这需要工作。)

在Rust中,该语言使得仅使用(n+1) size_of(T)个单词分配即可实现旋转。令我惊讶的是,我找不到旋转的标准库支持。 (std::mem中没有旋转方法)。可能看起来像这样:

pub fn rotate<T>(x: &mut T, y: &mut T, z: &mut T) {
    unsafe {
        let mut t: T = std::mem::uninitialized();
        std::ptr::copy_nonoverlapping(&*z, &mut t, 1);
        std::ptr::copy_nonoverlapping(&*y, z, 1);
        std::ptr::copy_nonoverlapping(&*x, y, 1);
        std::ptr::copy_nonoverlapping(&t, x, 1);
        std::mem::forget(t);
    }
}

要弄清为什么不能在C ++中有效实现旋转,请考虑:

struct String {
  char *data1;
  char *data2;
  String(String &&other) : data1(other.data1), data2(other.data2)
    { other.data1 = other.data2 = nullptr;}
  String &operator=(String &&other)
    { std::swap(data1, other.data1); std::swap(data2, other.data2);
      return *this; }
  ~String() { delete [] data1; delete [] data2; }
};

在这里,像s2 = std::move(s1);这样的操作将为每个成员字段分配3个指针,总共6个赋值,因为指针交换需要3个赋值(1个变为温度,1个变为温度,一个跨操作数)

1 个答案:

答案 0 :(得分:4)

  

Rust中有循环旋转可变变量的标准方法吗?

否。

我只需交换两次变量,不需要unsafe

use std::mem;

pub fn rotate<T>(x: &mut T, y: &mut T, z: &mut T) {
    mem::swap(x, y);
    mem::swap(y, z);
}

fn main() {
    let mut a = 1;
    let mut b = 2;
    let mut c = 3;

    println!("{}, {}, {}", a, b, c);
    // 1, 2, 3

    rotate(&mut a, &mut b, &mut c);

    println!("{}, {}, {}", a, b, c);
    // 2, 3, 1
}

这会产生7条movl指令(Rust 1.35.0,发行版,x86_64,Linux)

playground::rotate:
    movl    (%rdi), %eax
    movl    (%rsi), %ecx
    movl    %ecx, (%rdi)
    movl    %eax, (%rsi)
    movl    (%rdx), %ecx
    movl    %ecx, (%rsi)
    movl    %eax, (%rdx)
    retq

与最初的6条movl指令相反:

playground::rotate_original:
    movl    (%rdx), %eax
    movl    (%rsi), %ecx
    movl    %ecx, (%rdx)
    movl    (%rdi), %ecx
    movl    %ecx, (%rsi)
    movl    %eax, (%rdi)
    retq

我可以放弃那条指令来获取纯粹的安全代码,这也更易于推理。


在“真实”代码中,我利用了以下事实:所有变量都是同一类型,并且slice::rotate_leftslice::rotate_right存在:

fn main() {
    let mut vals = [1, 2, 3];

    let [a, b, c] = &vals;
    println!("{}, {}, {}", a, b, c);
    // 1, 2, 3

    vals.rotate_left(1);

    let [a, b, c] = &vals;
    println!("{}, {}, {}", a, b, c);
    // 2, 3, 1
}