Skip to main content

almost_enough/
func.rs

1//! Function-based cancellation.
2//!
3//! This module provides a [`Stop`] implementation that wraps a closure.
4//! Works in `no_std` environments.
5//!
6//! # Example
7//!
8//! ```rust
9//! use almost_enough::{FnStop, Stop};
10//! use core::sync::atomic::{AtomicBool, Ordering};
11//!
12//! static CANCELLED: AtomicBool = AtomicBool::new(false);
13//!
14//! let stop = FnStop::new(|| CANCELLED.load(Ordering::Relaxed));
15//!
16//! assert!(!stop.should_stop());
17//!
18//! CANCELLED.store(true, Ordering::Relaxed);
19//! assert!(stop.should_stop());
20//! ```
21//!
22//! # Integration with Other Systems
23//!
24//! `FnStop` is useful for bridging to external cancellation mechanisms:
25//!
26//! ```rust,ignore
27//! use almost_enough::{FnStop, Stop};
28//!
29//! // Bridge to tokio CancellationToken
30//! let tokio_token = tokio_util::sync::CancellationToken::new();
31//! let stop = FnStop::new({
32//!     let t = tokio_token.clone();
33//!     move || t.is_cancelled()
34//! });
35//!
36//! // Bridge to crossbeam channel
37//! let (tx, rx) = crossbeam_channel::bounded::<()>(1);
38//! let stop = FnStop::new(move || rx.try_recv().is_ok());
39//! ```
40
41use crate::{Stop, StopReason};
42
43/// A [`Stop`] implementation backed by a closure.
44///
45/// The closure should return `true` when the operation should stop.
46///
47/// # Example
48///
49/// ```rust
50/// use almost_enough::{FnStop, Stop};
51/// use core::sync::atomic::{AtomicBool, Ordering};
52///
53/// let flag = AtomicBool::new(false);
54/// let stop = FnStop::new(|| flag.load(Ordering::Relaxed));
55///
56/// assert!(!stop.should_stop());
57///
58/// flag.store(true, Ordering::Relaxed);
59/// assert!(stop.should_stop());
60/// ```
61pub struct FnStop<F> {
62    f: F,
63}
64
65impl<F> FnStop<F>
66where
67    F: Fn() -> bool + Send + Sync,
68{
69    /// Create a new function-based stop.
70    ///
71    /// The function should return `true` when the operation should stop.
72    #[inline]
73    pub fn new(f: F) -> Self {
74        Self { f }
75    }
76}
77
78impl<F> Stop for FnStop<F>
79where
80    F: Fn() -> bool + Send + Sync,
81{
82    #[inline]
83    #[track_caller]
84    fn check(&self) -> Result<(), StopReason> {
85        if (self.f)() {
86            Err(StopReason::Cancelled)
87        } else {
88            Ok(())
89        }
90    }
91
92    #[inline]
93    #[track_caller]
94    fn should_stop(&self) -> bool {
95        (self.f)()
96    }
97}
98
99impl<F: Clone> Clone for FnStop<F> {
100    fn clone(&self) -> Self {
101        Self { f: self.f.clone() }
102    }
103}
104
105impl<F: Copy> Copy for FnStop<F> {}
106
107impl<F> core::fmt::Debug for FnStop<F> {
108    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109        f.debug_struct("FnStop").finish_non_exhaustive()
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use core::sync::atomic::{AtomicBool, Ordering};
117
118    #[test]
119    fn fn_stop_basic() {
120        let flag = AtomicBool::new(false);
121        let stop = FnStop::new(|| flag.load(Ordering::Relaxed));
122
123        assert!(!stop.should_stop());
124        assert!(stop.check().is_ok());
125
126        flag.store(true, Ordering::Relaxed);
127
128        assert!(stop.should_stop());
129        assert_eq!(stop.check(), Err(StopReason::Cancelled));
130    }
131
132    #[test]
133    fn fn_stop_with_static() {
134        static FLAG: AtomicBool = AtomicBool::new(false);
135
136        let stop = FnStop::new(|| FLAG.load(Ordering::Relaxed));
137        assert!(!stop.should_stop());
138
139        FLAG.store(true, Ordering::Relaxed);
140        assert!(stop.should_stop());
141
142        // Reset for other tests
143        FLAG.store(false, Ordering::Relaxed);
144    }
145
146    #[test]
147    fn fn_stop_always_true() {
148        let stop = FnStop::new(|| true);
149        assert!(stop.should_stop());
150        assert_eq!(stop.check(), Err(StopReason::Cancelled));
151    }
152
153    #[test]
154    fn fn_stop_always_false() {
155        let stop = FnStop::new(|| false);
156        assert!(!stop.should_stop());
157        assert!(stop.check().is_ok());
158    }
159
160    #[test]
161    fn fn_stop_is_send_sync() {
162        fn assert_send_sync<T: Send + Sync>() {}
163        assert_send_sync::<FnStop<fn() -> bool>>();
164    }
165
166    #[test]
167    fn fn_stop_copy() {
168        // Note: closures that borrow aren't Copy, but fn pointers are
169        let stop: FnStop<fn() -> bool> = FnStop::new(|| false);
170        let stop2 = stop; // Copy, not Clone
171        assert!(!stop.should_stop()); // Original still usable
172        assert!(!stop2.should_stop());
173    }
174}
175
176#[cfg(all(test, feature = "alloc"))]
177mod alloc_tests {
178    use super::*;
179
180    #[test]
181    fn fn_stop_debug() {
182        extern crate alloc;
183        let stop = FnStop::new(|| false);
184        let debug = alloc::format!("{:?}", stop);
185        assert!(debug.contains("FnStop"));
186    }
187}