如何使用(不安全的)别名?

How to use (unsafe) aliasing?

Rust 有严格的别名规则。但是如果 "I know what I'm doing"?

我可以解决它们吗?

我正在尝试将一个 C 函数转换为 Rust,该函数通过从输入缓冲区读取并写入目标缓冲区来执行复杂的操作,但它有一个巧妙的优化,允许输入和输出缓冲区相同:

foo(src, dst); // result is written to dst
foo(buf, buf); // legal in C, does the operation in-place

为了这个问题,我们假设它是这样的:

void inplace(char *src, char *dst, int len) {
   for(int i=0; i < len-1; i++) {
      dst[i] = src[i+1] * 2; // algorithm works even if src == dst
   }
}

在 Rust 的安全子集中​​,我必须有两个几乎复制和粘贴的函数版本 fn(&mut)fn(&, &mut)

有没有办法欺骗 Rust 以获得对同一缓冲区的可变和不可变引用?

Rust 不允许您参数化可变性,不。

理论上,您可以编写一些为指针设置别名的不安全代码,但您必须直接使用原始指针。

&mut 暗示指针没有别名,优化器会这样对待它。使用一个原始指针和一个 &mut 指针仍然会导致问题。

您的主要功能必须使用不安全的代码来实现才能使用原始指针。原始指针允许您绕过 Rust 的别名规则。然后,您可以使用两个函数充当此不安全实现的安全外观。

unsafe fn foo(src: *const u8, dst: *mut u8, len: usize) {
    for i in 0..len - 1 {
        *dst.offset(i as isize) = *src.offset(i as isize + 1) * 2;
    }
}

fn foo_inplace(buf: &mut [u8]) {
    unsafe { foo(buf.as_ptr(), buf.as_mut_ptr(), buf.len()) }
}

fn foo_separate(src: &[u8], dst: &mut [u8]) {
    assert!(src.len() == dst.len());
    unsafe { foo(src.as_ptr(), dst.as_mut_ptr(), src.len()) }
}

fn main() {
    let src = &[0, 1, 2, 3, 4, 5];
    let dst = &mut [0, 0, 0, 0, 0, 0];

    let buf = &mut [11, 22, 33, 44, 55, 66];

    foo_separate(src, dst);
    foo_inplace(buf);

    println!("src: {:?}", src);
    println!("dst: {:?}", dst);
    println!("buf: {:?}", buf);
}

as_ptr()as_mut_ptr()len()slices.

上的方法

您可以使用宏在安全代码中实现这一点。它适用于所有具有 len 函数并支持索引的参数。这基本上是鸭子打字。

macro_rules! inplace(
    ($a:ident, $b:ident) => (for i in 0..($a.len()-1) {
        $a[i] = $b[i + 1] * 2;
    })
);

fn main() {
    let mut arr = [1, 2, 3, 4, 5];
    inplace!(arr, arr);
    println!("{:?}", arr);
}

产出

[4, 6, 8, 10, 5]

不,你不能在安全的 Rust 中这样做。如果您愿意,可以使用不安全代码来解决别名限制,但是...

but it has a clever optimization that allows the input and output buffer to be the same

你称之为优化,我称之为悲观。

当保证两个缓冲区不相同时,优化器可以对您的代码进行矢量化。这意味着循环的比较次数减少了 4 倍或 8 倍,大大加快了更大输入的执行速度。

然而,在没有别名信息的情况下,它必须悲观地假设输入可能被别名,因此无法进行此类优化。更糟糕的是,不知道 如何 它们是别名,它甚至不知道是 &dst[i] == &src[i-1] 还是 &dst[i] == &src[i] 还是 &dst[i] == &src[i+1];这意味着预取已经结束等等...


然而,在安全的 Rust 中,此信息可用。它确实会强制您编写两个例程(一个用于单个输入,一个用于两个输入)但是都可以相应地进行优化。