How to use (unsafe) aliasing?

早过忘川 提交于 2019-12-13 13:11:17

问题


Rust has strict aliasing rules. But can I work around them if "I know what I'm doing"?

I'm trying to convert to Rust a C function that performs a complicated operation by reading from input buffer and writing to a destination buffer, but it has a clever optimization that allows the input and output buffer to be the same:

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

For the sake of the question let's say it's something like:

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
   }
}

In safe subset of Rust I'd have to have two nearly copy & pasted versions of the function fn(&mut) and fn(&, &mut).

Is there a way to cheat Rust to get both mutable and immutable reference to the same buffer?


回答1:


Your main function will have to be implemented using unsafe code in order to use raw pointers. Raw pointers allow you to bypass Rust's aliasing rules. You can then have two functions that act as safe façades for this unsafe implementation.

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() and len() are methods on slices.




回答2:


No, you cannot do so in safe Rust. You can use unsafe code to work around aliasing limitations if you wish to but...

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

what you call an optimization, I call a pessimization.

When the two buffers are guaranteed not to be the same, the optimizer can vectorize your code. It means 4x or 8x less comparisons for the loop, greatly speeding up the execution for larger inputs.

In the absence of aliasing information, however, it must pessimistically assume that the inputs could be aliased and therefore cannot do such optimization. Worse, not knowing how they are aliased, it does not even know whether &dst[i] == &src[i-1] or &dst[i] == &src[i] or &dst[i] == &src[i+1]; it means pre-fetching is out etc...


In safe Rust, however, this information is available. It does force you to write two routines (one for a single input, one for two inputs) but both can be optimized accordingly.




回答3:


Rust does not allow you to parameterize over mutabilty, no.

In theory, you could write some unsafe code that aliases pointers, but you'd have to use raw pointers directly.

&mut implies that the pointer is not aliased, and the optimizer will treat it as such. Using one raw pointer and one &mut pointer can still cause problems.




回答4:


You can use a macro to achieve this in safe code. It'll work for all arguments that have a len function and support indexing. This is basically duck-typing.

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);
}

outputs

[4, 6, 8, 10, 5]



来源:https://stackoverflow.com/questions/30406852/how-to-use-unsafe-aliasing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!