Анализатор заимствований
Анализатор заимствований (borrow checker) Rust накладывает ограничения на способы заимствования значений. Мы уже видели, что ссылка не может пережить значение, которое она заимствует:
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
fn main() {
let x_ref = {
let x = 10;
&x
};
dbg!(x_ref);
}
Есть также второе основное правило, которое проверяет анализатор заимствований: правило алиасинга. Для заданного значения в любой момент времени:
- Можно иметь одну или несколько разделяемых ссылок на значение, или
- Можно иметь ровно одну эксклюзивную ссылку на значение.
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
fn main() {
let mut a = 10;
let b = &a;
{
let c = &mut a;
*c = 20;
}
dbg!(a);
dbg!(b);
}
This slide should take about 10 minutes.
- Правило «пережить» мы уже демонстрировали, когда впервые рассматривали ссылки. Здесь мы повторяем его, чтобы показать студентам, что анализатор заимствований следует нескольким разным правилам.
- Приведённый выше код не компилируется, потому что
aодновременно заимствуется как изменяемое (черезc) и как неизменяемое (черезb).- Обратите внимание, что требуется, чтобы конфликтующих ссылок не существовало в одной точке. Неважно, где происходит разыменование ссылки. Попробуйте закомментировать
*c = 20и покажите, что ошибка компилятора всё равно возникает, даже если мы никогда не используемc. - Обратите внимание, что промежуточная ссылка
cне нужна, чтобы вызвать конфликт заимствований. Заменитеcпрямым изменениемaи покажите, что это даёт похожую ошибку. Так происходит, потому что прямое изменение значения фактически создаёт временную изменяемую ссылку.
- Обратите внимание, что требуется, чтобы конфликтующих ссылок не существовало в одной точке. Неважно, где происходит разыменование ссылки. Попробуйте закомментировать
- Переместите оператор
dbg!дляbперед областью, которая вводитc, чтобы код скомпилировался.- После этого изменения компилятор понимает, что
bиспользуется только до нового изменяемого заимствованияaчерезc. Это особенность анализатора заимствований, называемая «non-lexical lifetimes» (времена жизни, не ограниченные лексически).
- После этого изменения компилятор понимает, что
Для дальнейшего изучения
- Технически несколько изменяемых ссылок на один фрагмент данных могут существовать одновременно благодаря повторному заимствованию (re-borrowing). Именно это позволяет передать изменяемую ссылку в функцию, не делая исходную ссылку недействительной. Этот пример на playground демонстрирует такое поведение.
- Rust использует ограничение на эксклюзивные ссылки, чтобы не допустить гонок данных в многопоточном коде: только один поток может иметь изменяемый доступ к данным в каждый момент времени.
- Rust также использует это ограничение для оптимизации кода. Например, значение за разделяемой ссылкой можно безопасно кэшировать в регистре на время жизни этой ссылки.
- Поля структуры можно заимствовать независимо друг от друга, но вызов метода структуры заимствует всю структуру, потенциально делая недействительными ссылки на отдельные поля. Пример см. в этом фрагменте на playground.