Правильно ли выполняется псевдоним изменяемых ссылок в небезопасном коде? - PullRequest
0 голосов
/ 11 февраля 2019

В небезопасном коде, правильно ли иметь несколько изменяемых ссылок (не указателей) на один и тот же массив, если они не используются для записи в одни и те же индексы?

Контекст

Я хотел бы получить несколько (различных) изменяемых представлений базового массива, которые я могу изменять из разных потоков.

Если непересекающиеся части являются смежными, это просто, простовызов 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]);

Но я также хочу раскрыть несмежные непересекающиеся части.Для простоты, скажем, мы хотим получить изменяемое «представление» для четных индексов и другое изменяемое «представление» для нечетных индексов.

В отличие от split_at_mut(), мы не смогли получить две изменяемые ссылки (нам нужнобезопасная абстракция!), поэтому мы используем вместо этого два экземпляра структуры, предоставляя только изменчивый доступ к четным (или нечетным) индексам:

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

С некоторым кодом небезопасным это легкочтобы получить такую ​​безопасную абстракцию.Вот возможная реализация :

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

Для этого примера это может не стоить, но для более сложного кода было бы гораздо удобнее вместо этого использовать кусочкинеобработанных указателей.

Вопрос

Это правильно?

Это явно нарушает правила заимствования : два потока могут одновременно содержать изменяемую ссылку ната же самая область памяти.Однако они могут никогда не записывать в одни и те же индексы.

Изменяемый псевдоним ссылки не указан как небезопасная сверхдержава , но список не представлен как исчерпывающий.

Ответы [ 2 ]

0 голосов
/ 11 февраля 2019

Документация для UnsafeCell гласит:

Тип UnsafeCell<T> является единственным допустимым способом получения псевдонимов, которые считаются изменяемыми.
[...]
Компилятор выполняет оптимизацию, основываясь на знании того, что &T не является псевдонимом или мутированным, а &mut T уникален.

Так что нет, то, что вы пытаетесь сделать, недопустимо, если вы не используете UnsafeCell.

0 голосов
/ 11 февраля 2019

Является ли псевдоним изменяемых ссылок правильным

Нет, это никогда не корректно для псевдонимов изменяемых ссылок (изменяемые указатели более нюансированыконцепция).Это нарушает одно из основных правил ссылок .

Ни одна из предоставленных вами квалификаций не имеет значения - вы не можете иметь изменяемый псевдоним ссылок.Код, находящийся внутри блока unsafe, не имеет значения.Это 1011 * автоматическое и мгновенное неопределенное поведение .


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 Документы явно вызывают это:

Ссылка &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");
    }
}
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::<()>`
...