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, который активируется только в отладочных сборках.