drop_bomb/
lib.rs

1//! # drop_bomb
2//!
3//! `drop_bomb` provides two types, `DropBomb` and `DebugDropBomb`,
4//! which panic in `drop` with a specified message unless
5//! defused. This is useful as a building-block for runtime-checked
6//! linear types.
7//!
8//! For example, one can build a variant of `BufWriter` which enforces
9//! handling of errors during flush.
10//!
11//! ```rust
12//! extern crate drop_bomb;
13//!
14//! use std::io::{Write, BufWriter, Result};
15//! use drop_bomb::DropBomb;
16//!
17//! struct CheckedBufWriter<W: Write> {
18//!     inner: BufWriter<W>,
19//!     bomb: DropBomb,
20//! }
21//!
22//! impl<W: Write> CheckedBufWriter<W> {
23//!     fn new(inner: BufWriter<W>) -> CheckedBufWriter<W> {
24//!         let bomb = DropBomb::new(
25//!             "CheckedBufWriter must be explicitly closed \
26//!              to handle potential errors on flush"
27//!         );
28//!         CheckedBufWriter { inner, bomb }
29//!     }
30//!
31//!     fn close(mut self) -> Result<()> {
32//!         self.bomb.defuse();
33//!         self.inner.flush()?;
34//!         Ok(())
35//!     }
36//! }
37//! ```
38//!
39//! ## Notes:
40//!
41//! * Bombs do nothing if a thread is already panicking.
42//! * When `#[cfg(debug_assertions)]` is disabled, `DebugDropBomb` is
43//!   always defused and has a zero size.
44use std::borrow::Cow;
45
46#[derive(Debug)]
47#[must_use]
48pub struct DropBomb(RealBomb);
49
50impl DropBomb {
51    pub fn new(msg: impl Into<Cow<'static, str>>) -> DropBomb {
52        DropBomb(RealBomb::new(msg.into()))
53    }
54    pub fn defuse(&mut self) {
55        self.set_defused(true)
56    }
57    pub fn set_defused(&mut self, defused: bool) {
58        self.0.set_defused(defused)
59    }
60    pub fn is_defused(&self) -> bool {
61        self.0.is_defused()
62    }
63}
64
65#[derive(Debug)]
66#[must_use]
67pub struct DebugDropBomb(DebugBomb);
68
69impl DebugDropBomb {
70    pub fn new(msg: impl Into<Cow<'static, str>>) -> DebugDropBomb {
71        DebugDropBomb(DebugBomb::new(msg.into()))
72    }
73    pub fn defuse(&mut self) {
74        self.set_defused(true)
75    }
76    pub fn set_defused(&mut self, defused: bool) {
77        self.0.set_defused(defused)
78    }
79    pub fn is_defused(&self) -> bool {
80        self.0.is_defused()
81    }
82}
83
84#[cfg(debug_assertions)]
85type DebugBomb = RealBomb;
86#[cfg(not(debug_assertions))]
87type DebugBomb = FakeBomb;
88
89#[derive(Debug)]
90struct RealBomb {
91    msg: Cow<'static, str>,
92    defused: bool,
93}
94
95impl RealBomb {
96    fn new(msg: Cow<'static, str>) -> RealBomb {
97        RealBomb {
98            msg: msg.into(),
99            defused: false,
100        }
101    }
102    fn set_defused(&mut self, defused: bool) {
103        self.defused = defused
104    }
105    fn is_defused(&self) -> bool {
106        self.defused
107    }
108}
109
110impl Drop for RealBomb {
111    fn drop(&mut self) {
112        if !self.defused && !::std::thread::panicking() {
113            panic!("{}", self.msg)
114        }
115    }
116}
117
118#[derive(Debug)]
119#[cfg(not(debug_assertions))]
120struct FakeBomb {}
121
122#[cfg(not(debug_assertions))]
123impl FakeBomb {
124    fn new(_msg: Cow<'static, str>) -> FakeBomb {
125        FakeBomb {}
126    }
127    fn set_defused(&mut self, _defused: bool) {}
128    fn is_defused(&self) -> bool {
129        true
130    }
131}
132
133#[cfg(not(debug_assertions))]
134impl Drop for FakeBomb {
135    fn drop(&mut self) {}
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    #[should_panic(expected = "Kaboom")]
144    fn armed_bomb_bombs() {
145        let _b = DropBomb::new("Kaboom");
146    }
147
148    #[test]
149    fn defused_bomb_is_safe() {
150        let mut b = DropBomb::new("Kaboom");
151        assert!(!b.is_defused());
152        b.defuse();
153        assert!(b.is_defused());
154    }
155
156    #[test]
157    #[should_panic(expected = r#"printf("sucks to be you"); exit(666);"#)]
158    fn no_double_panics() {
159        let _b = DropBomb::new("Kaboom");
160        panic!(r#"printf("sucks to be you"); exit(666);"#)
161    }
162
163    #[test]
164    #[should_panic(expected = "Kaboom")]
165    #[cfg(debug_assertions)]
166    fn debug_bomb_bombs_if_debug() {
167        let _b = DebugDropBomb::new("Kaboom");
168    }
169
170    #[test]
171    #[cfg(not(debug_assertions))]
172    fn debug_bomb_bombs_if_debug() {
173        let _b = DebugDropBomb::new("Kaboom");
174    }
175
176    #[test]
177    fn defused_bomb_is_safe_if_debug() {
178        let mut b = DebugDropBomb::new("Kaboom");
179        #[cfg(debug_assertions)]
180        assert!(!b.is_defused());
181        #[cfg(not(debug_assertions))]
182        assert!(b.is_defused());
183        b.defuse();
184        assert!(b.is_defused());
185    }
186
187    #[test]
188    #[should_panic(expected = r#"printf("sucks to be you"); exit(666);"#)]
189    fn no_double_panics_if_debug() {
190        let _b = DebugDropBomb::new("Kaboom");
191        panic!(r#"printf("sucks to be you"); exit(666);"#)
192    }
193
194    #[test]
195    #[cfg(not(debug_assertions))]
196    fn debug_bomb_is_zst() {
197        assert_eq!(::std::mem::size_of::<DebugDropBomb>(), 0);
198    }
199
200    #[test]
201    fn check_traits() {
202        fn assert_traits<T: ::std::fmt::Debug + Send + Sync>() {}
203        assert_traits::<DropBomb>();
204        assert_traits::<DebugDropBomb>();
205    }
206}