Skip to main content

pubhubs/misc/
drop_ext.rs

1//! A [`Bomb`] runs a callback when dropped, unless it is [`defuse`](Bomb::defuse)d first.
2
3/// Runs `f` when dropped — like [`defer`](crate::misc::defer()) — unless it has been
4/// [`defuse`](Bomb::defuse)d first.
5///
6/// Handy for "do this *unless* we get there cleanly": arm the bomb, then [`defuse`](Bomb::defuse)
7/// it on every success path, so `f` runs only when the bomb is dropped early — e.g. an owning task is
8/// cancelled, or a `?`/panic unwinds past it.
9///
10/// For the common "this *must* happen before drop, or it is a bug" case, see [`Bomb::panic`].
11#[must_use = "a Bomb fires at once unless bound to a variable"]
12pub struct Bomb<F: FnOnce()> {
13    callback: Option<F>,
14}
15
16impl<F: FnOnce()> Bomb<F> {
17    /// Arms a bomb that runs `f` when dropped.
18    pub fn new(f: F) -> Self {
19        Self { callback: Some(f) }
20    }
21
22    /// Defuses the bomb so `f` will not run (dropping `f`, and so releasing whatever it captured).
23    pub fn defuse(&mut self) {
24        self.callback = None;
25    }
26}
27
28impl Bomb<Box<dyn FnOnce()>> {
29    /// Arms a bomb that logs `msg` and then panics when dropped (unless the thread is already
30    /// panicking) — i.e. asserts that the bomb is [`defuse`](Bomb::defuse)d before it is dropped.
31    pub fn panic(msg: impl FnOnce() -> String + 'static) -> Self {
32        Self::new(Box::new(move || {
33            let message = msg();
34
35            log::error!("{message}");
36
37            if !std::thread::panicking() {
38                panic!("{message}");
39            }
40        }))
41    }
42}
43
44impl<F: FnOnce()> Drop for Bomb<F> {
45    fn drop(&mut self) {
46        if let Some(f) = self.callback.take() {
47            f();
48        }
49    }
50}