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

Drop bomb: обеспечение корректности API

Используйте Drop, чтобы обеспечивать инварианты и выявлять некорректное использование API. «Drop bomb» вызывает панику, если значение освобождается, не будучи явно завершённым.

Этот паттерн часто используется, когда операция завершения (например, commit() или rollback()) должна возвращать Result, что нельзя сделать из Drop.

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

use std::io::{self, Write};

struct Transaction {
    active: bool,
}

impl Transaction {
    fn start() -> Self {
        Self { active: true }
    }

    fn commit(mut self) -> io::Result<()> {
        writeln!(io::stdout(), "COMMIT")?;
        self.active = false;
        Ok(())
    }
}

impl Drop for Transaction {
    fn drop(&mut self) {
        if self.active {
            panic!("Transaction dropped without commit!");
        }
    }
}

fn main() -> io::Result<()> {
    let tx = Transaction::start();
    // Используйте `tx`, чтобы построить транзакцию, затем зафиксируйте её.
    // Закомментируйте вызов `commit`, чтобы увидеть панику.
    tx.commit()?;
    Ok(())
}
  • В некоторых системах значение должно быть завершено определённым API до того, как его освободят.

    Например, Transaction может потребоваться зафиксировать (commit) или откатить (rollback).

  • Drop bomb гарантирует, что значение вроде Transaction не может быть тихо освобождено в незавершённом состоянии. Деструктор вызывает панику, если транзакция не была явно завершена (например, через commit()).

  • Операция завершения (например, commit()) обычно принимает self по значению. Это гарантирует, что после завершения транзакции исходный объект уже нельзя использовать.

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

  • Этот паттерн уместен даже в публичных API. Он помогает пользователям заметить ошибки на раннем этапе, когда они забывают явно завершить транзакционный объект.

  • Если очистку можно безопасно выполнить в Drop, некоторые API решают паниковать только в отладочных сборках. Уместность этого зависит от гарантий, которые должен обеспечивать ваш API.

  • Паника в релизных сборках разумна, когда тихое неправильное использование привело бы к серьёзным проблемам корректности или безопасности.

  • Вопрос: зачем нам нужен флаг active внутри Transaction? Почему drop() не может паниковать безусловно?

    Ожидаемый ответ: commit() принимает self по значению и запускает drop(), который вызвал бы панику.

Для дальнейшего изучения

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

  • Крейт drop_bomb: небольшая утилита, которая вызывает панику при освобождении, если её явно не обезвредить с помощью .defuse(). Есть вариант DebugDropBomb, который активируется только в отладочных сборках.