为什么通过原始指针修改可变引用的值不违反Rust的别名规则?

lcm*_*lin 8 pointers unsafe rust borrowing

我没有锈的别名规则特别深刻的理解(从我听说他们没有扎实的定义),但我无法理解是什么使这个代码示例std::slice文档还好.我在这里重复一遍:

let x = &mut [1, 2, 4];
let x_ptr = x.as_mut_ptr();

unsafe {
    for i in 0..x.len() {
        *x_ptr.offset(i as isize) += 2;
    }
}
assert_eq!(x, &[3, 4, 6]);
Run Code Online (Sandbox Code Playgroud)

我在这里看到的问题是x,作为&mut参考,可以假设编译器是唯一的.xget 的内容经过修改x_ptr,然后通过回读x,我认为没有理由为什么编译器不能假设x没有被修改,因为它从未通过唯一的现有&mut引用进行修改.

那么,我在这里错过了什么?

  • 编译器是否需要假设*mut T可能是别名&mut T,即使通常允许它假设&mut T 永远不会别名别名&mut T

  • unsafe块是否充当某种别名障碍,编译器假定其中的代码可能已修改了范围内的任何内容?

  • 此代码示例是否已损坏?

如果有某种稳定的规则使这个例子没问题,究竟是什么呢?它的范围是多少?我应该担心多少假设破坏unsafeRust代码中的随机事物?

Mat*_* M. 8

免责声明:尚无正式的记忆模型.1

首先,我想谈谈:

我在这里看到的问题是x,作为&mut参考,可以假设编译器是唯一的.

是的......没有.如果不借用,x只能被认为是独一无二的,这是一个重要的区别:

fn doit(x: &mut T) {
    let y = &mut *x;
    //  x is re-borrowed at this point.
}
Run Code Online (Sandbox Code Playgroud)

因此,目前,我会假设从某种意义上导出指针x将暂时"借用" x.

当然,在没有正式模型的情况下,这一切都是荒谬的,这也是为什么rustc编译器对于别名优化没有过于激进的部分原因:直到定义了正式模型,并且检查了代码以匹配它,优化必须保守.

1 RustBelt项目就是为Rust建立一个经过正式验证的内存模型.Ralf Jung的最新消息是关于Stacked Borrows模型.


来自Ralf(评论):上面例子中的关键点是有一个明确的转移x,x_ptr然后x再转回.x_ptr从某种意义上说,这是一个范围借用.应使用去x,x_ptr,回x再换x_ptr,那么后者将是不确定的行为:

fn main() {
    let x = &mut [1, 2, 4];
    let x_ptr = x.as_mut_ptr(); // x_ptr borrows the right to mutate

    unsafe {
        for i in 0..x.len() {
            *x_ptr.offset(i as isize) += 2; // Fine use of raw pointer.
        }
    }
    assert_eq!(x, &[3, 4, 6]);  // x is back in charge, x_ptr invalidated.

    unsafe { *x_ptr += 1; }     // BÄM! Used no-longer-valid raw pointer.
}
Run Code Online (Sandbox Code Playgroud)

  • 实际上,关键的一点是`x_ptr`是*从*`x'派生而且`x`由于创建了`x_ptr`而未被使用.这两个都必须是正确的,因为这个代码是正确的. (2认同)