Rust有严格的别名规则。但是,如果“我知道我在做什么”,我可以解决它们吗?
我正在尝试转换为Rust一个C函数,它通过从输入缓冲区读取并写入目标缓冲区来执行复杂的操作,但它有一个聪明的优化,允许输入和输出缓冲区相同:< / p>
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的安全子集中,我必须有两个近乎副本&amp;函数fn(&mut)
和fn(&, &mut)
的粘贴版本。
有没有办法欺骗Rust以获得对同一缓冲区的可变和不可变引用?
答案 0 :(得分:5)
不,你不能在安全的Rust中这样做。如果您愿意,可以使用不安全的代码来解决别名限制......
但它有一个聪明的优化,允许输入和输出缓冲区相同
你称之为优化,我称之为悲观。
当保证两个缓冲区不相同时,优化器可以对代码进行矢量化。这意味着循环的比较少4倍或8倍,大大加快了大输入的执行速度。
然而,在没有别名信息的情况下,它必须悲观地假设输入可能是别名的,因此无法进行此类优化。更糟糕的是,不知道如何他们是别名,它甚至不知道&dst[i] == &src[i-1]
或&dst[i] == &src[i]
或&dst[i] == &src[i+1]
;这意味着预取等等...
然而,在安全的Rust中,此信息可用。它会强制您编写两个例程(一个用于单个输入,一个用于两个输入),但两者都可以相应地进行优化。
答案 1 :(得分:4)
Rust不允许你参数化mutabilty,no。
理论上,您可以编写一些不安全的代码来指定别名,但是您必须直接使用原始指针。
&mut
表示指针没有别名,优化器会将其视为等效。使用一个原始指针和一个&mut
指针仍然可能导致问题。
答案 2 :(得分:4)
您可以使用宏来实现安全代码。它适用于具有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]
答案 3 :(得分:2)
您的主要功能必须使用不安全的代码才能使用原始指针。原始指针允许您绕过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上的方法。