Почему изменение значения изменяемой ссылки с помощью необработанного указателя не нарушает правила псевдонима Rust?

У меня нет особенно четкого понимания правил псевдонима Rust (и, насколько я слышал, они четко не определены), но мне трудно понять, что делает этот пример кода в 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]);

Проблема, которую я вижу здесь, заключается в том, что x, будучи &mut ссылкой, может считаться компилятором уникальным. Содержимое x изменяется с помощью x_ptr, а затем считывается с помощью x, и я не вижу причин, по которым компилятор не мог просто предположить, что x не был изменен, поскольку он никогда не изменялся с помощью единственной существующей ссылки &mut.

Итак, что мне здесь не хватает?

  • Обязан ли компилятор предполагать, что *mut T может быть псевдонимом &mut T, хотя обычно разрешено предполагать, что &mut T никогда не использует псевдоним другого &mut T?

  • Блок unsafe действует как своего рода барьер псевдонима, когда компилятор предполагает, что код внутри него мог изменить что-либо в области видимости?

  • Этот пример кода не работает?

Если есть какое-то стабильное правило, которое делает этот пример приемлемым, что именно? Какова его степень? Насколько я должен беспокоиться о том, что предположения о псевдониме нарушают случайные вещи в unsafe коде Rust?


person lcmylin    schedule 28.09.2018    source источник
comment
Я думаю, что это LLVM, и поскольку x и x_ptr содержат адрес одного и того же типа, LLVM необходимо перезагрузить x   -  person Stargateur    schedule 28.09.2018
comment
@Stargateur Правда? У меня создалось впечатление, что анализ псевдонимов на основе типов позволяет LLVM делать более строгие предположения о несвязанности объектов одного типа в памяти.   -  person lcmylin    schedule 28.09.2018
comment
@Mylin: по памяти TBAA является включенным (внешний интерфейс должен выдавать определенные атрибуты), а rustc не поддерживает. Вместо этого он использует аннотации для каждой переменной.   -  person Matthieu M.    schedule 28.09.2018
comment
Действительно, Rust НЕ делает никаких рассуждений, основанных на указанном типе (кроме проверки внутренней изменчивости). Так что то, что написал @Stargateur, неверно для Rust.   -  person Ralf Jung    schedule 30.09.2018


Ответы (1)


Отказ от ответственности: пока нет формальной модели памяти. 1

Прежде всего, я хотел бы обратиться к:

Проблема, которую я вижу здесь, заключается в том, что x, будучи ссылкой &mut, может считаться компилятором уникальной.

Да и нет. x можно считать уникальным, только если не заимствовано, важное отличие:

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

Поэтому в настоящее время я буду работать с предположением, что получение указателя из x в некотором смысле временно "заимствует" x.

Конечно, это все бессмысленно из-за отсутствия формальной модели, и это одна из причин, по которой компилятор rustc пока не слишком агрессивен с оптимизацией псевдонимов: пока формальная модель не определена и код не проверен на соответствие ей, оптимизации должны быть консервативными.

1 Целью проекта RustBelt является создание официально проверенной модели памяти для Rust. Последние новости от Ральфа Юнга касались Модель с накоплением заимствований.


От Ральфа (комментарии): ключевым моментом в приведенном выше примере является четкий переход от x к x_ptr и снова обратно к x. Так что x_ptr - это в некотором смысле заимствование с ограниченным объемом. Если использование изменится на x, x_ptr, обратно на x и обратно на x_ptr, то последним будет Undefined Behavior:

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.
}
person Matthieu M.    schedule 28.09.2018
comment
В самом деле, ключевым моментом является то, что x_ptr является производным от x И x не использовался с момента создания x_ptr. Оба эти условия должны быть верными, чтобы этот код был правильным. - person Ralf Jung; 30.09.2018
comment
Возможно, стоит добавить пример вроде play.rust-lang.org/, показывая, что повторное использование x_ptr после использования x запрещено. - person Ralf Jung; 30.09.2018
comment
@RalfJung Это не разрешено, но assert_eq!(x, &[3, 4, 6]); сразу после того, как последняя строка выходит из строя и сообщает, что она изменилась на _2 _... Итак, мы вернулись к проблемам, которых Rust был создан, чтобы избежать, просто не определяя, что правильно, а что нет? Если компилятор не оптимизирует (я построил его в режиме выпуска, то же самое), он прямо сейчас не сломает его (похоже, поэтому я получаю правильные результаты, как если бы я использовал C), тогда в чем вообще смысл эти произвольные правила? Для меня это большая проблема, когда невозможно понять, что не так и что можно делать ... - person Sahsahae; 04.08.2019
comment
Чтобы быть полностью ясным: &mut + &mut = compile error, это единственное, что очевидно ... Я попал туда, пытаясь выяснить, &mut + *mut = wrong (здесь мы утверждаем, что это неправильно), и действительно ли *mut + *mut = wrong (я еще не нашел ничего, что упоминает об этом). Если еще нет четких правил, то это UB, а не UB (tm)? - person Sahsahae; 04.08.2019
comment
Это UB, и в некоторых случаях он используется. Некоторые оптимизации временно отключены, потому что ошибок LLVM. Но то, что компилятор в настоящее время не распознает ваш код максимально возможным способом, не означает, что он не станет лучше в будущем. Вы не можете ожидать, что компилятор с самого начала сделает все самые агрессивные оптимизации. В C обычным подходом кажется оптимизация, пока кто-то не пожалуется на ошибку; мы хотели бы сначала убедиться, что знаем, что правильно. - person Ralf Jung; 05.08.2019
comment
@Sahsahae также, unsafe Rust действительно разделяет многие проблемы неопределенного поведения с C и C ++. Ценность Rust заключается в способности скрыть небезопасность за абстракцией и локализовать ее. Сравните std::vector в C ++ и Vec в Rust: их реализация очень похожа и одинаково опасна для обоих языков. Но как пользователь существует огромная разница: в C ++ вам нужно постоянно беспокоиться о недействительности итератора и т. Д., В Rust вы знаете, что компилятор вас поддержал. - person Ralf Jung; 05.08.2019