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}