gpt4 book ai didi

rust - 不安全代码中可变引用的别名是否正确?

转载 作者:行者123 更新时间:2023-11-29 07:49:21 27 4
gpt4 key购买 nike

不安全代码中,对同一个数组有多个可变引用(不是指针)是否正确,只要它们不用于写入相同的索引?

上下文

我想生成一个底层数组的几个(不同的)可变 View ,我可以从不同的线程进行修改。

如果不相交的部分是连续的,这很简单,只需调用 split_at_mut在切片上:

let mut v = [1, 2, 3, 4];
{
let (left, right) = v.split_at_mut(2);
left[0] = 5;
right[0] = 6;
}
assert!(v == [5, 2, 6, 4]);

但我也想公开不连续的不相交部分。为简单起见,假设我们要为偶数索引检索一个可变“ View ”,为奇数索引检索另一个可变“ View ”。

split_at_mut() 相反,我们无法检索两个可变引用(我们想要一个安全的抽象!),因此我们使用两个结构实例来代替,仅公开对 even(resp.odd)的可变访问) 索引:

let data = &mut [0i32; 11];
let (mut even, mut odd) = split_fields(data);
// …

有了一些不安全的代码,很容易得到这样一个安全的抽象。这是 a possible implementation :

use std::marker::PhantomData;

struct SliceField<'a> {
ptr: *mut i32,
len: usize,
field: usize,
marker: PhantomData<&'a mut i32>,
}

impl SliceField<'_> {
fn inc(&mut self) {
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) += 1;
}
}
}

fn dec(&mut self) {
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) -= 1;
}
}
}
}

unsafe impl Send for SliceField<'_> {}

fn split_fields(array: &mut [i32]) -> (SliceField<'_>, SliceField<'_>) {
(
SliceField {
ptr: array.as_mut_ptr(),
len: array.len(),
field: 0,
marker: PhantomData,
},
SliceField {
ptr: array.as_mut_ptr(),
len: array.len(),
field: 1,
marker: PhantomData,
},
)
}

fn main() {
let data = &mut [0i32; 11];
{
let (mut even, mut odd) = split_fields(data);
rayon::join(|| even.inc(), || odd.dec());
}
// this prints [1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1]
println!("{:?}", data);
}

到目前为止,还不错。

问题

但是,访问原始指针要方便得多:与切片相反,我们不能使用运算符 [] 或迭代器。

unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) += 1;
}
}

显而易见的想法是在不安全的实现中将原始指针本地转换为切片:

let slice = unsafe { slice::from_raw_parts_mut(self.ptr, self.len) };

然后我们可以,例如,以函数式风格重写我们的实现:

slice.iter_mut().skip(self.field).step_by(2).for_each(|x| *x += 1);

对于这个示例,它可能不值得,但对于更复杂的代码,使用切片而不是原始指针会方便得多。

问题

这是正确的吗?

这显然违反了 borrowing rules :两个线程可能同时持有对同一内存位置的可变引用。但是,它们可能永远不会写入相同的索引。

可变引用别名未列为 unsafe superpower ,但列表并未详尽无遗。

最佳答案

Is aliasing of mutable references correct

不,给可变引用起别名永远不正确(可变指针是一个更微妙的概念)。这打破了其中一个主要rules of references .

您提供的所有条件都无关紧要 - 您不能使用可变的引用别名。 unsafe block 中的代码没有区别。这样做是自动和即时的未定义行为


fn main() {
let mut x = [42, 84];
let x_raw = &mut x as *mut _;

let x_even: &mut [i32; 2] = unsafe { &mut *x_raw };
let x_odd: &mut [i32; 2] = unsafe { &mut *x_raw };

println!("{}, {}", x_even[0], x_odd[1]);
}

美里说:

error[E0080]: constant evaluation error: Borrow being dereferenced (Uniq(1772)) does not exist on the stack
--> src/main.rs:8:24
|
8 | println!("{}, {}", x_even[0], x_odd[1]);
| ^^^^^^^^^ Borrow being dereferenced (Uniq(1772)) does not exist on the stack
|
= note: inside call to `main` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:34
= note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:53
= note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:297:40
= note: inside call to `std::panicking::try::do_call::<[closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:293:5
= note: inside call to `std::panicking::try::<i32, [closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe]>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:388:9
= note: inside call to `std::panic::catch_unwind::<[closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:25
= note: inside call to `std::rt::lang_start_internal` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:5
= note: inside call to `std::rt::lang_start::<()>`

虽然 UnsafeCell可以帮助您构建安全的抽象,您仍然必须遵守引用规则。将类型替换为 UnsafeCell不会使事情神奇地工作:

use std::cell::UnsafeCell;

fn main() {
let x = UnsafeCell::new([42, 84]);

let x_even: &mut [i32; 2] = unsafe { &mut *x.get() };
let x_odd: &mut [i32; 2] = unsafe { &mut *x.get() };

println!("{}, {}", x_even[0], x_odd[1]);
}
error[E0080]: constant evaluation error: Borrow being dereferenced (Uniq(1776)) does not exist on the stack
--> src/main.rs:9:24
|
9 | println!("{}, {}", x_even[0], x_odd[1]);
| ^^^^^^^^^ Borrow being dereferenced (Uniq(1776)) does not exist on the stack
|
= note: inside call to `main` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:34
= note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:53
= note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:297:40
= note: inside call to `std::panicking::try::do_call::<[closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:293:5
= note: inside call to `std::panicking::try::<i32, [closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe]>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:388:9
= note: inside call to `std::panic::catch_unwind::<[closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:25
= note: inside call to `std::rt::lang_start_internal` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:5
= note: inside call to `std::rt::lang_start::<()>`

UnsafeCell 的文档明确指出:

A &mut T reference may be released to safe code provided neither other &mut T nor &T co-exist with it. A &mut T must always be unique.

事实上,即使您的切片不在同一点开始但它们以某种方式重叠,这也是混叠和未定义的行为:

fn main() {
let mut x = [0, 1, 2];
let x_raw = &mut x as *mut [i32];

let x_0: &mut [i32] = unsafe { &mut (*x_raw)[0..2] };
let x_1: &mut [i32] = unsafe { &mut (*x_raw)[1..3] };

if x_0 == x_1 {
println!("They are equal");
}
}
error[E0080]: constant evaluation error: Borrow being dereferenced (Uniq(1807)) does not exist on the stack
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/cmp.rs:1041:65
|
1041 | fn eq(&self, other: &&'b mut B) -> bool { PartialEq::eq(*self, *other) }
| ^^^^^ Borrow being dereferenced (Uniq(1807)) does not exist on the stack
|
note: inside call to `std::cmp::impls::<impl std::cmp::PartialEq<&'b mut B> for &'a mut A><[i32], [i32]>::eq` at src/main.rs:8:8
--> src/main.rs:8:8
|
8 | if x_0 == x_1 {
| ^^^^^^^^^^
= note: inside call to `main` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:34
= note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:53
= note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:297:40
= note: inside call to `std::panicking::try::do_call::<[closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:293:5
= note: inside call to `std::panicking::try::<i32, [closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe]>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:388:9
= note: inside call to `std::panic::catch_unwind::<[closure@DefId(1/1:1900 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:25
= note: inside call to `std::rt::lang_start_internal` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:5
= note: inside call to `std::rt::lang_start::<()>`

关于rust - 不安全代码中可变引用的别名是否正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54633474/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com