enough/lib.rs
1//! # enough
2//!
3//! Minimal cooperative cancellation trait for long-running operations.
4//!
5//! This crate provides the core [`Stop`] trait that codec authors and library
6//! writers can use to support cancellation. It is `no_std` with zero dependencies.
7//!
8//! ## For Library Authors
9//!
10//! Accept `impl Stop` in your long-running functions:
11//!
12//! ```rust
13//! use enough::{Stop, StopReason};
14//!
15//! pub fn decode(data: &[u8], stop: impl Stop) -> Result<Vec<u8>, DecodeError> {
16//! let mut output = Vec::new();
17//! for (i, chunk) in data.chunks(1024).enumerate() {
18//! // Check periodically in hot loops
19//! if i % 16 == 0 {
20//! stop.check()?;
21//! }
22//! // process chunk...
23//! output.extend_from_slice(chunk);
24//! }
25//! Ok(output)
26//! }
27//!
28//! #[derive(Debug)]
29//! pub enum DecodeError {
30//! Stopped(StopReason),
31//! InvalidData,
32//! }
33//!
34//! impl From<StopReason> for DecodeError {
35//! fn from(r: StopReason) -> Self { DecodeError::Stopped(r) }
36//! }
37//! ```
38//!
39//! ## Zero-Cost When Not Needed
40//!
41//! Use [`Never`] when you don't need cancellation:
42//!
43//! ```rust
44//! use enough::Never;
45//!
46//! // Compiles to nothing - zero runtime cost
47//! // let result = my_codec::decode(&data, Never);
48//! ```
49//!
50//! ## Implementations
51//!
52//! This crate provides only the trait and a zero-cost `Never` implementation.
53//! For concrete cancellation primitives (`Stopper`, `StopSource`, timeouts, etc.),
54//! see the [`almost-enough`](https://docs.rs/almost-enough) crate.
55//!
56//! ## Feature Flags
57//!
58//! - **None (default)** - Core trait only, `no_std` compatible
59//! - **`std`** - Adds `std::error::Error` impl for `StopReason`
60
61#![cfg_attr(not(feature = "std"), no_std)]
62#![warn(missing_docs)]
63#![warn(clippy::all)]
64
65#[cfg(feature = "alloc")]
66extern crate alloc;
67
68mod reason;
69
70pub use reason::StopReason;
71
72/// Cooperative cancellation check.
73///
74/// Implement this trait for custom cancellation sources. The implementation
75/// must be thread-safe (`Send + Sync`) to support parallel processing and
76/// async runtimes.
77///
78/// # Example Implementation
79///
80/// ```rust
81/// use enough::{Stop, StopReason};
82/// use core::sync::atomic::{AtomicBool, Ordering};
83///
84/// pub struct MyStop<'a> {
85/// cancelled: &'a AtomicBool,
86/// }
87///
88/// impl Stop for MyStop<'_> {
89/// fn check(&self) -> Result<(), StopReason> {
90/// if self.cancelled.load(Ordering::Relaxed) {
91/// Err(StopReason::Cancelled)
92/// } else {
93/// Ok(())
94/// }
95/// }
96/// }
97/// ```
98pub trait Stop: Send + Sync {
99 /// Check if the operation should stop.
100 ///
101 /// Returns `Ok(())` to continue, `Err(StopReason)` to stop.
102 ///
103 /// Call this periodically in long-running loops. The frequency depends
104 /// on your workload - typically every 16-1000 iterations is reasonable.
105 fn check(&self) -> Result<(), StopReason>;
106
107 /// Returns `true` if the operation should stop.
108 ///
109 /// Convenience method for when you want to handle stopping yourself
110 /// rather than using the `?` operator.
111 #[inline]
112 fn should_stop(&self) -> bool {
113 self.check().is_err()
114 }
115}
116
117/// A [`Stop`] implementation that never stops.
118///
119/// This is a zero-cost type for callers who don't need cancellation support.
120/// All methods are inlined and optimized away.
121///
122/// # Example
123///
124/// ```rust
125/// use enough::{Stop, Never};
126///
127/// fn process(data: &[u8], stop: impl Stop) -> Vec<u8> {
128/// // ...
129/// # vec![]
130/// }
131///
132/// // Caller doesn't need cancellation
133/// let data = [1u8, 2, 3];
134/// let result = process(&data, Never);
135/// ```
136#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
137pub struct Never;
138
139impl Stop for Never {
140 #[inline(always)]
141 fn check(&self) -> Result<(), StopReason> {
142 Ok(())
143 }
144
145 #[inline(always)]
146 fn should_stop(&self) -> bool {
147 false
148 }
149}
150
151// Blanket impl: &T where T: Stop
152impl<T: Stop + ?Sized> Stop for &T {
153 #[inline]
154 fn check(&self) -> Result<(), StopReason> {
155 (**self).check()
156 }
157
158 #[inline]
159 fn should_stop(&self) -> bool {
160 (**self).should_stop()
161 }
162}
163
164// Blanket impl: &mut T where T: Stop
165impl<T: Stop + ?Sized> Stop for &mut T {
166 #[inline]
167 fn check(&self) -> Result<(), StopReason> {
168 (**self).check()
169 }
170
171 #[inline]
172 fn should_stop(&self) -> bool {
173 (**self).should_stop()
174 }
175}
176
177#[cfg(feature = "alloc")]
178impl<T: Stop + ?Sized> Stop for alloc::boxed::Box<T> {
179 #[inline]
180 fn check(&self) -> Result<(), StopReason> {
181 (**self).check()
182 }
183
184 #[inline]
185 fn should_stop(&self) -> bool {
186 (**self).should_stop()
187 }
188}
189
190#[cfg(feature = "alloc")]
191impl<T: Stop + ?Sized> Stop for alloc::sync::Arc<T> {
192 #[inline]
193 fn check(&self) -> Result<(), StopReason> {
194 (**self).check()
195 }
196
197 #[inline]
198 fn should_stop(&self) -> bool {
199 (**self).should_stop()
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn never_does_not_stop() {
209 assert!(!Never.should_stop());
210 assert!(Never.check().is_ok());
211 }
212
213 #[test]
214 fn never_is_copy() {
215 let a = Never;
216 let b = a; // Copy
217 let _ = a; // Still valid
218 let _ = b;
219 }
220
221 #[test]
222 fn never_is_default() {
223 let _: Never = Default::default();
224 }
225
226 #[test]
227 fn reference_impl_works() {
228 let never = Never;
229 let reference: &dyn Stop = &never;
230 assert!(!reference.should_stop());
231 }
232
233 #[test]
234 fn stop_reason_from_impl() {
235 // Test that From<StopReason> pattern works
236 #[derive(Debug, PartialEq)]
237 #[allow(dead_code)]
238 enum TestError {
239 Stopped(StopReason),
240 Other,
241 }
242
243 impl From<StopReason> for TestError {
244 fn from(r: StopReason) -> Self {
245 TestError::Stopped(r)
246 }
247 }
248
249 fn might_stop(stop: impl Stop) -> Result<(), TestError> {
250 stop.check()?;
251 Ok(())
252 }
253
254 assert!(might_stop(Never).is_ok());
255 }
256
257 #[test]
258 fn dyn_stop_works() {
259 fn process(stop: &dyn Stop) -> bool {
260 stop.should_stop()
261 }
262
263 let never = Never;
264 assert!(!process(&never));
265 }
266}