如何使用(不安全的)别名?
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 中,此信息可用。它确实会强制您编写两个例程(一个用于单个输入,一个用于两个输入)但是都可以相应地进行优化。
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 中,此信息可用。它确实会强制您编写两个例程(一个用于单个输入,一个用于两个输入)但是都可以相应地进行优化。