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

Взаимоисключающие ссылки / «Алиасинг XOR изменяемость»

Мы можем использовать взаимное исключение ссылок &T и &mut T, чтобы не допустить использования данных до того, как они будут готовы.

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct QueryResult;
pub struct DatabaseConnection {/* поля опущены */}

impl DatabaseConnection {
    pub fn new() -> Self {
        Self {}
    }
    pub fn results(&self) -> &[QueryResult] {
        &[] // фиктивные результаты
    }
}

pub struct Transaction<'a> {
    connection: &'a mut DatabaseConnection,
}

impl<'a> Transaction<'a> {
    pub fn new(connection: &'a mut DatabaseConnection) -> Self {
        Self { connection }
    }
    pub fn query(&mut self, _query: &str) {
        // Отправляем запрос, но не ждём результатов.
    }
    pub fn commit(self) {
        // Завершаем выполнение транзакции и получаем результаты.
    }
}

fn main() {
    let mut db = DatabaseConnection::new();

    // Транзакция `tx` изменяемо заимствует `db`.
    let mut tx = Transaction::new(&mut db);
    tx.query("SELECT * FROM users");

    // Это не скомпилируется, потому что `db` уже изменяемо заимствован транзакцией `tx`.
    // let results = db.results(); // ❌🔨

    // Заимствование `db` заканчивается, когда `tx` потребляется методом `commit()`.
    tx.commit();

    // Теперь можно снова заимствовать `db`.
    let results = db.results();
}
  • Мотивация: в этом API базы данных запросы запускаются на асинхронное выполнение, а результаты становятся доступны только после завершения всей транзакции.

    Пользователь может подумать, что запросы выполняются сразу, и попытаться прочитать результаты до того, как они станут доступны. Такое неправильное использование API может привести к тому, что приложение прочитает неполные или некорректные данные.

    Хотя это очевидное недоразумение, подобные ситуации случаются на практике.

    Спросите: кто-нибудь неправильно понимал API, не читая документацию по правильному использованию?

    Ожидаемый ответ: примеры ошибок и недоразумений на начальном этапе карьеры или в университете.

    По мере роста API и числа пользователей всё меньший процент пользователей обладает глубоким знанием системы, которую представляет API.

  • Этот пример показывает, как можно использовать Aliasing XOR Mutability, чтобы предотвратить такое неправильное использование.

  • Код может прочитать результаты до их готовности, если программист предполагает, что запросы выполняются сразу, а не запускаются на асинхронное выполнение.

  • Конструктор типа Transaction принимает изменяемую ссылку на подключение к базе данных и сохраняет её в возвращаемом значении Transaction.

    Явное время жизни здесь не должно пугать: в данном случае оно означает лишь, что DatabaseConnection, переданное в конструктор, живёт дольше Transaction.

    Ссылка изменяемая, чтобы полностью заблокировать DatabaseConnection для любого другого использования, например для запуска новых транзакций или чтения результатов.

  • Пока существует Transaction, мы не можем обращаться к переменной DatabaseConnection, из которой он был создан.

    Продемонстрируйте: раскомментируйте строку db.results(). Это приведёт к ошибке компиляции, так как db уже изменяемо заимствован.

  • Примечание: то, что результаты запросов не являются публичными и доступны только через функцию-геттер, позволяет обеспечить инвариант «пользователи могут смотреть результаты запросов, только если нет активных транзакций».

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