为什么通过原始指针修改可变引用的值不会违反 Rust 的别名规则?
Why does modifying a mutable reference's value through a raw pointer not violate Rust's aliasing rules?
我对 Rust 的别名规则没有特别透彻的理解(据我所知,它们的定义并不明确),但我很难理解是什么让 this code example 在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]);
我在这里看到的问题是,作为 &mut
引用的 x
可以被编译器假定为唯一的。 x
的内容通过 x_ptr
修改,然后通过 x
读回,我看不出为什么编译器不能假设 x
没有已被修改,因为它从未通过唯一现有的 &mut
参考进行修改。
那么,我在这里缺少什么?
编译器是否需要假定 *mut T
可能是 &mut T
的别名,即使通常允许假定 &mut T
永远不会 别名另一个 &mut T
?
unsafe
块是否充当某种别名屏障,编译器假定其中的代码可能修改了范围内的任何内容?
这个代码示例有问题吗?
如果有某种稳定的规则使这个例子没问题,那具体是什么?它的范围是什么?我应该担心在 unsafe
Rust 代码中破坏随机事物的别名假设吗?
免责声明:还没有正式的内存模型。1
首先,我想说:
The problem I see here is that x
, being an &mut
reference, can be assumed to be unique by the compiler.
是的……但不是。 x
只能假设是唯一的如果不是借来的,一个重要的区别:
fn doit(x: &mut T) {
let y = &mut *x;
// x is re-borrowed at this point.
}
因此,目前,我会假设从 x
派生指针在某种意义上会暂时 "borrow" x
。
当然,在没有正式模型的情况下,这一切都是空想,这也是 rustc 编译器在别名优化方面还不太积极的部分原因:直到定义了正式模型,并检查了代码为了匹配它,优化必须是保守的。
1 RustBelt 项目旨在为 Rust 建立一个经过正式验证的内存模型。 Ralf Jung 的最新消息是关于 Stacked Borrows model.
来自 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.
}
我对 Rust 的别名规则没有特别透彻的理解(据我所知,它们的定义并不明确),但我很难理解是什么让 this code example 在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]);
我在这里看到的问题是,作为 &mut
引用的 x
可以被编译器假定为唯一的。 x
的内容通过 x_ptr
修改,然后通过 x
读回,我看不出为什么编译器不能假设 x
没有已被修改,因为它从未通过唯一现有的 &mut
参考进行修改。
那么,我在这里缺少什么?
编译器是否需要假定
*mut T
可能是&mut T
的别名,即使通常允许假定&mut T
永远不会 别名另一个&mut T
?unsafe
块是否充当某种别名屏障,编译器假定其中的代码可能修改了范围内的任何内容?这个代码示例有问题吗?
如果有某种稳定的规则使这个例子没问题,那具体是什么?它的范围是什么?我应该担心在 unsafe
Rust 代码中破坏随机事物的别名假设吗?
免责声明:还没有正式的内存模型。1
首先,我想说:
The problem I see here is that
x
, being an&mut
reference, can be assumed to be unique by the compiler.
是的……但不是。 x
只能假设是唯一的如果不是借来的,一个重要的区别:
fn doit(x: &mut T) {
let y = &mut *x;
// x is re-borrowed at this point.
}
因此,目前,我会假设从 x
派生指针在某种意义上会暂时 "borrow" x
。
当然,在没有正式模型的情况下,这一切都是空想,这也是 rustc 编译器在别名优化方面还不太积极的部分原因:直到定义了正式模型,并检查了代码为了匹配它,优化必须是保守的。
1 RustBelt 项目旨在为 Rust 建立一个经过正式验证的内存模型。 Ralf Jung 的最新消息是关于 Stacked Borrows model.
来自 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.
}