Skip to main content

almost_enough/
boxed.rs

1//! Boxed dynamic dispatch for Stop.
2//!
3//! This module provides [`BoxedStop`], a heap-allocated wrapper that enables
4//! dynamic dispatch without monomorphization bloat.
5//!
6//! # Deprecated
7//!
8//! Use [`StopToken`](crate::StopToken): it checks through the same fast
9//! paths and is `Clone`. `BoxedStop` wraps one; it adds nothing but the
10//! absence of `Clone`.
11//!
12//! # Alternatives
13//!
14//! For borrowed dynamic dispatch with zero allocation, use `&dyn Stop`:
15//!
16//! ```rust
17//! use almost_enough::{StopSource, Stop};
18//!
19//! fn process(stop: &dyn Stop) {
20//!     if stop.should_stop() {
21//!         return;
22//!     }
23//!     // ...
24//! }
25//!
26//! let source = StopSource::new();
27//! process(&source);
28//! ```
29
30#![allow(deprecated)] // This module defines and tests the deprecated type.
31
32use crate::{Stop, StopReason, StopToken};
33
34/// A type-erased [`Stop`] with unique ownership. Deprecated: use
35/// [`StopToken`], which checks the same way and is `Clone`.
36///
37/// `BoxedStop` wraps a `StopToken`: no-op stops (like `Unstoppable`) are stored
38/// as nothing and never dispatched, a `Stopper` is checked as a direct atomic
39/// load without a vtable, a `SyncStopper` keeps its own `Arc` behind the
40/// vtable, and wrapping a `StopToken` or another `BoxedStop` reuses it instead
41/// of nesting. Anything else is allocated and checked through a vtable.
42///
43/// # Example
44///
45/// ```rust
46/// use almost_enough::{BoxedStop, StopSource, Stopper, Unstoppable, Stop};
47///
48/// fn process(stop: BoxedStop) {
49///     for i in 0..1000 {
50///         if i % 100 == 0 && stop.should_stop() {
51///             return;
52///         }
53///         // process...
54///     }
55/// }
56///
57/// // Works with any Stop implementation
58/// process(BoxedStop::new(Unstoppable));
59/// process(BoxedStop::new(StopSource::new()));
60/// process(BoxedStop::new(Stopper::new()));
61/// ```
62#[deprecated(
63    since = "0.4.5",
64    note = "use `StopToken`, which checks the same way and is `Clone`"
65)]
66pub struct BoxedStop(pub(crate) StopToken);
67
68impl BoxedStop {
69    /// Create a new boxed stop from any [`Stop`] implementation.
70    ///
71    /// No-op stops (where `may_stop()` returns false) are not allocated —
72    /// `check()` will short-circuit to `Ok(())`. See [`StopToken::new`] for
73    /// the other cases that don't allocate.
74    #[inline]
75    pub fn new<T: Stop + 'static>(stop: T) -> Self {
76        Self(StopToken::new(stop))
77    }
78}
79
80impl Stop for BoxedStop {
81    #[inline(always)]
82    #[track_caller]
83    fn check(&self) -> Result<(), StopReason> {
84        self.0.check()
85    }
86
87    #[inline(always)]
88    #[track_caller]
89    fn should_stop(&self) -> bool {
90        self.0.should_stop()
91    }
92
93    #[inline(always)]
94    fn may_stop(&self) -> bool {
95        self.0.may_stop()
96    }
97}
98
99impl core::fmt::Debug for BoxedStop {
100    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101        f.debug_tuple("BoxedStop").finish()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::{StopSource, Stopper, Unstoppable};
109
110    #[test]
111    fn boxed_stop_from_unstoppable() {
112        let stop = BoxedStop::new(Unstoppable);
113        assert!(!stop.should_stop());
114        assert!(stop.check().is_ok());
115        assert!(!stop.may_stop());
116    }
117
118    #[test]
119    fn boxed_stop_from_stopper() {
120        let stopper = Stopper::new();
121        let stop = BoxedStop::new(stopper.clone());
122
123        assert!(!stop.should_stop());
124
125        stopper.cancel();
126
127        assert!(stop.should_stop());
128        assert_eq!(stop.check(), Err(StopReason::Cancelled));
129    }
130
131    #[test]
132    fn boxed_stop_is_send_sync() {
133        fn assert_send_sync<T: Send + Sync>() {}
134        assert_send_sync::<BoxedStop>();
135    }
136
137    #[test]
138    fn boxed_stop_debug() {
139        let stop = BoxedStop::new(Unstoppable);
140        let debug = alloc::format!("{:?}", stop);
141        assert!(debug.contains("BoxedStop"));
142    }
143
144    #[test]
145    fn boxed_stop_avoids_monomorphization() {
146        fn process(stop: BoxedStop) -> bool {
147            stop.should_stop()
148        }
149
150        assert!(!process(BoxedStop::new(Unstoppable)));
151        assert!(!process(BoxedStop::new(StopSource::new())));
152        assert!(!process(BoxedStop::new(Stopper::new())));
153    }
154
155    #[test]
156    fn may_stop_delegates_through_boxed() {
157        assert!(!BoxedStop::new(Unstoppable).may_stop());
158        assert!(BoxedStop::new(Stopper::new()).may_stop());
159    }
160
161    #[test]
162    fn unstoppable_no_allocation() {
163        // Unstoppable wraps to None — no heap allocation
164        let stop = BoxedStop::new(Unstoppable);
165        assert!(!stop.may_stop());
166        assert!(stop.check().is_ok());
167    }
168}