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

rom*_*m1v 5 unsafe mutable pointer-aliasing rust

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

上下文

我想产生几个(不同的)底层数组的可变视图,我可以从不同的线程修改.

如果不相交的部分是连续的,那么通过调用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]);
Run Code Online (Sandbox Code Playgroud)

但我也希望揭露不连续的不相交部分.为简单起见,假设我们想为偶数索引检索可变"视图",为奇数索引检索另一个可变"视图".

相反split_at_mut(),我们无法检索两个可变引用(我们想要一个安全的抽象!),所以我们使用两个结构实例,只暴露偶数(相应的奇数)索引的可变访问:

let data = &mut [0i32; 11];
let (mut even, mut odd) = split_fields(data);
// …
Run Code Online (Sandbox Code Playgroud)

使用一些不安全的代码,很容易获得这样一个安全的抽象.这是一个可能的实现:

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);
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

问题

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

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

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

let slice = unsafe { slice::from_raw_parts_mut(self.ptr, self.len) };
Run Code Online (Sandbox Code Playgroud)

然后,我们可以,例如,在功能样式中重写我们的实现:

slice.iter_mut().skip(self.field).step_by(2).for_each(|x| *x += 1);
Run Code Online (Sandbox Code Playgroud)

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

它是否正确?

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

可变参考别名未列为不安全的超级大国,但该列表并未列为详尽无遗.

She*_*ter 10

可变引用的别名是否正确

不,别名可变引用永远不正确(可变指针是一个更细微的概念).这打破了一个主要的参考规则.

您提供的资格都没有关系 - 您不能拥有可变的参考别名.代码在unsafe块内部没有区别.这样做是自动和即时的未定义行为.


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]);
}
Run Code Online (Sandbox Code Playgroud)

美里说:

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::<()>`
Run Code Online (Sandbox Code Playgroud)

虽然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]);
}
Run Code Online (Sandbox Code Playgroud)
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::<()>`
Run Code Online (Sandbox Code Playgroud)

UnsafeCell的文档明确地称之为:

&mut T参考可被释放到安全码提供既不其他&mut T也不&T与之共存.A &mut T必须始终是唯一的.

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

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");
    }
}
Run Code Online (Sandbox Code Playgroud)
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::<()>`
Run Code Online (Sandbox Code Playgroud)