Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Анализатор заимствований

Анализатор заимствований (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.