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    fn check(&self) -> Result<(), StopReason> {
84        if (self.f)() {
85            Err(StopReason::Cancelled)
86        } else {
87            Ok(())
88        }
89    }
90
91    #[inline]
92    fn should_stop(&self) -> bool {
93        (self.f)()
94    }
95}
96
97impl<F: Clone> Clone for FnStop<F> {
98    fn clone(&self) -> Self {
99        Self { f: self.f.clone() }
100    }
101}
102
103impl<F: Copy> Copy for FnStop<F> {}
104
105impl<F> core::fmt::Debug for FnStop<F> {
106    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        f.debug_struct("FnStop").finish_non_exhaustive()
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use core::sync::atomic::{AtomicBool, Ordering};
115
116    #[test]
117    fn fn_stop_basic() {
118        let flag = AtomicBool::new(false);
119        let stop = FnStop::new(|| flag.load(Ordering::Relaxed));
120
121        assert!(!stop.should_stop());
122        assert!(stop.check().is_ok());
123
124        flag.store(true, Ordering::Relaxed);
125
126        assert!(stop.should_stop());
127        assert_eq!(stop.check(), Err(StopReason::Cancelled));
128    }
129
130    #[test]
131    fn fn_stop_with_static() {
132        static FLAG: AtomicBool = AtomicBool::new(false);
133
134        let stop = FnStop::new(|| FLAG.load(Ordering::Relaxed));
135        assert!(!stop.should_stop());
136
137        FLAG.store(true, Ordering::Relaxed);
138        assert!(stop.should_stop());
139
140        // Reset for other tests
141        FLAG.store(false, Ordering::Relaxed);
142    }
143
144    #[test]
145    fn fn_stop_always_true() {
146        let stop = FnStop::new(|| true);
147        assert!(stop.should_stop());
148        assert_eq!(stop.check(), Err(StopReason::Cancelled));
149    }
150
151    #[test]
152    fn fn_stop_always_false() {
153        let stop = FnStop::new(|| false);
154        assert!(!stop.should_stop());
155        assert!(stop.check().is_ok());
156    }
157
158    #[test]
159    fn fn_stop_is_send_sync() {
160        fn assert_send_sync<T: Send + Sync>() {}
161        assert_send_sync::<FnStop<fn() -> bool>>();
162    }
163
164    #[test]
165    fn fn_stop_clone() {
166        // Note: closures that borrow aren't Clone, but fn pointers are
167        let stop: FnStop<fn() -> bool> = FnStop::new(|| false);
168        let stop2 = stop.clone();
169        assert!(!stop2.should_stop());
170    }
171}
172
173#[cfg(all(test, feature = "alloc"))]
174mod alloc_tests {
175    use super::*;
176
177    #[test]
178    fn fn_stop_debug() {
179        extern crate alloc;
180        let stop = FnStop::new(|| false);
181        let debug = alloc::format!("{:?}", stop);
182        assert!(debug.contains("FnStop"));
183    }
184}