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}