Skip to main content

qubit_atomic/atomic/
atomic_count.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9
10//! # Atomic Count
11//!
12//! Provides a non-negative atomic counter for values whose transitions are
13//! used as synchronization signals.
14//!
15//! # Author
16//!
17//! Haixing Hu
18
19use std::fmt;
20use std::sync::atomic::{
21    AtomicUsize as StdAtomicUsize,
22    Ordering,
23};
24
25/// A non-negative atomic counter with synchronization-oriented operations.
26///
27/// Use this type when the counter value is part of a concurrent state machine,
28/// such as active task counts, in-flight request counts, resource usage counts,
29/// or shutdown and termination checks. Its operations return the value after
30/// the update, which makes zero-transition logic straightforward.
31///
32/// For pure metrics, statistics, event counters, or ID generation, prefer the
33/// regular atomic integer types such as [`Atomic<usize>`](crate::Atomic).
34/// Those types keep arithmetic operations lightweight for pure counting.
35///
36/// This counter never wraps. Incrementing past [`usize::MAX`] panics, and
37/// decrementing below zero panics. Use [`try_add`](Self::try_add),
38/// [`try_dec`](Self::try_dec), or [`try_sub`](Self::try_sub) when overflow or
39/// underflow is a normal business outcome.
40///
41/// # Example
42///
43/// ```rust
44/// use qubit_atomic::AtomicCount;
45///
46/// let active_tasks = AtomicCount::zero();
47///
48/// active_tasks.inc();
49/// assert!(!active_tasks.is_zero());
50///
51/// if active_tasks.dec() == 0 {
52///     // The last active task finished; notify termination waiters here.
53/// }
54/// ```
55///
56/// # Author
57///
58/// Haixing Hu
59#[repr(transparent)]
60pub struct AtomicCount {
61    /// Standard-library atomic storage for the non-negative counter value.
62    inner: StdAtomicUsize,
63}
64
65impl AtomicCount {
66    /// Creates a new non-negative atomic counter.
67    ///
68    /// # Parameters
69    ///
70    /// * `value` - The initial counter value.
71    ///
72    /// # Returns
73    ///
74    /// A counter initialized to `value`.
75    ///
76    /// # Example
77    ///
78    /// ```rust
79    /// use qubit_atomic::AtomicCount;
80    ///
81    /// let counter = AtomicCount::new(3);
82    /// assert_eq!(counter.get(), 3);
83    /// ```
84    #[inline]
85    pub const fn new(value: usize) -> Self {
86        Self {
87            inner: StdAtomicUsize::new(value),
88        }
89    }
90
91    /// Creates a new counter initialized to zero.
92    ///
93    /// # Returns
94    ///
95    /// A counter whose current value is zero.
96    ///
97    /// # Example
98    ///
99    /// ```rust
100    /// use qubit_atomic::AtomicCount;
101    ///
102    /// let counter = AtomicCount::zero();
103    /// assert!(counter.is_zero());
104    /// ```
105    #[inline]
106    pub const fn zero() -> Self {
107        Self::new(0)
108    }
109
110    /// Gets the current counter value.
111    ///
112    /// # Returns
113    ///
114    /// The current counter value.
115    ///
116    /// # Example
117    ///
118    /// ```rust
119    /// use qubit_atomic::AtomicCount;
120    ///
121    /// let counter = AtomicCount::new(7);
122    /// assert_eq!(counter.get(), 7);
123    /// ```
124    #[inline]
125    pub fn get(&self) -> usize {
126        self.inner.load(Ordering::Acquire)
127    }
128
129    /// Returns whether the current counter value is zero.
130    ///
131    /// # Returns
132    ///
133    /// `true` if the current value is zero, otherwise `false`.
134    ///
135    /// # Example
136    ///
137    /// ```rust
138    /// use qubit_atomic::AtomicCount;
139    ///
140    /// let counter = AtomicCount::zero();
141    /// assert!(counter.is_zero());
142    /// ```
143    #[inline]
144    pub fn is_zero(&self) -> bool {
145        self.get() == 0
146    }
147
148    /// Returns whether the current counter value is greater than zero.
149    ///
150    /// # Returns
151    ///
152    /// `true` if the current value is greater than zero, otherwise `false`.
153    ///
154    /// # Example
155    ///
156    /// ```rust
157    /// use qubit_atomic::AtomicCount;
158    ///
159    /// let counter = AtomicCount::new(1);
160    /// assert!(counter.is_positive());
161    /// ```
162    #[inline]
163    pub fn is_positive(&self) -> bool {
164        self.get() > 0
165    }
166
167    /// Increments the counter by one and returns the new value.
168    ///
169    /// # Returns
170    ///
171    /// The counter value after the increment.
172    ///
173    /// # Panics
174    ///
175    /// Panics if the increment would overflow [`usize::MAX`].
176    ///
177    /// # Example
178    ///
179    /// ```rust
180    /// use qubit_atomic::AtomicCount;
181    ///
182    /// let counter = AtomicCount::zero();
183    /// assert_eq!(counter.inc(), 1);
184    /// ```
185    #[inline]
186    pub fn inc(&self) -> usize {
187        self.add(1)
188    }
189
190    /// Adds `delta` to the counter and returns the new value.
191    ///
192    /// # Parameters
193    ///
194    /// * `delta` - The amount to add.
195    ///
196    /// # Returns
197    ///
198    /// The counter value after the addition.
199    ///
200    /// # Panics
201    ///
202    /// Panics if the addition would overflow [`usize::MAX`].
203    ///
204    /// # Example
205    ///
206    /// ```rust
207    /// use qubit_atomic::AtomicCount;
208    ///
209    /// let counter = AtomicCount::new(2);
210    /// assert_eq!(counter.add(3), 5);
211    /// ```
212    #[inline]
213    pub fn add(&self, delta: usize) -> usize {
214        self.try_add(delta).expect("atomic counter overflow")
215    }
216
217    /// Tries to add `delta` to the counter.
218    ///
219    /// # Parameters
220    ///
221    /// * `delta` - The amount to add.
222    ///
223    /// # Returns
224    ///
225    /// `Some(new_value)` if the addition succeeds, or `None` if it would
226    /// overflow [`usize::MAX`]. On `None`, the counter is left unchanged.
227    ///
228    /// # Example
229    ///
230    /// ```rust
231    /// use qubit_atomic::AtomicCount;
232    ///
233    /// let counter = AtomicCount::new(2);
234    /// assert_eq!(counter.try_add(3), Some(5));
235    /// ```
236    #[inline]
237    pub fn try_add(&self, delta: usize) -> Option<usize> {
238        self.try_update(|current| current.checked_add(delta))
239    }
240
241    /// Decrements the counter by one and returns the new value.
242    ///
243    /// This method is useful for detecting the transition to zero:
244    ///
245    /// ```rust
246    /// use qubit_atomic::AtomicCount;
247    ///
248    /// let counter = AtomicCount::new(1);
249    /// if counter.dec() == 0 {
250    ///     // This call consumed the final counted item.
251    /// }
252    /// ```
253    ///
254    /// # Returns
255    ///
256    /// The counter value after the decrement.
257    ///
258    /// # Panics
259    ///
260    /// Panics if the current value is zero.
261    #[inline]
262    pub fn dec(&self) -> usize {
263        self.try_dec().expect("atomic counter underflow")
264    }
265
266    /// Tries to decrement the counter by one.
267    ///
268    /// # Returns
269    ///
270    /// `Some(new_value)` if the decrement succeeds, or `None` if the current
271    /// value is zero. On `None`, the counter is left unchanged.
272    ///
273    /// # Example
274    ///
275    /// ```rust
276    /// use qubit_atomic::AtomicCount;
277    ///
278    /// let counter = AtomicCount::new(1);
279    /// assert_eq!(counter.try_dec(), Some(0));
280    /// assert_eq!(counter.try_dec(), None);
281    /// ```
282    #[inline]
283    pub fn try_dec(&self) -> Option<usize> {
284        self.try_sub(1)
285    }
286
287    /// Subtracts `delta` from the counter and returns the new value.
288    ///
289    /// # Parameters
290    ///
291    /// * `delta` - The amount to subtract.
292    ///
293    /// # Returns
294    ///
295    /// The counter value after the subtraction.
296    ///
297    /// # Panics
298    ///
299    /// Panics if the subtraction would make the counter negative.
300    ///
301    /// # Example
302    ///
303    /// ```rust
304    /// use qubit_atomic::AtomicCount;
305    ///
306    /// let counter = AtomicCount::new(5);
307    /// assert_eq!(counter.sub(2), 3);
308    /// ```
309    #[inline]
310    pub fn sub(&self, delta: usize) -> usize {
311        self.try_sub(delta).expect("atomic counter underflow")
312    }
313
314    /// Tries to subtract `delta` from the counter.
315    ///
316    /// # Parameters
317    ///
318    /// * `delta` - The amount to subtract.
319    ///
320    /// # Returns
321    ///
322    /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
323    /// make the counter negative. On `None`, the counter is left unchanged.
324    ///
325    /// # Example
326    ///
327    /// ```rust
328    /// use qubit_atomic::AtomicCount;
329    ///
330    /// let counter = AtomicCount::new(3);
331    /// assert_eq!(counter.try_sub(2), Some(1));
332    /// assert_eq!(counter.try_sub(2), None);
333    /// ```
334    #[inline]
335    pub fn try_sub(&self, delta: usize) -> Option<usize> {
336        self.try_update(|current| current.checked_sub(delta))
337    }
338
339    /// Applies a checked update with synchronization semantics.
340    ///
341    /// # Parameters
342    ///
343    /// * `update` - A function that maps the current value to the next value,
344    ///   or returns `None` to reject the update.
345    ///
346    /// # Returns
347    ///
348    /// `Some(new_value)` if the update succeeds, or `None` if `update`
349    /// rejects the current value. A rejected update leaves the counter
350    /// unchanged.
351    #[inline]
352    fn try_update<F>(&self, update: F) -> Option<usize>
353    where
354        F: Fn(usize) -> Option<usize>,
355    {
356        let mut current = self.get();
357        loop {
358            let next = update(current)?;
359            match self.inner.compare_exchange_weak(
360                current,
361                next,
362                Ordering::AcqRel,
363                Ordering::Acquire,
364            ) {
365                Ok(_) => return Some(next),
366                Err(actual) => current = actual,
367            }
368        }
369    }
370}
371
372impl Default for AtomicCount {
373    /// Creates a zero-valued atomic counter.
374    ///
375    /// # Returns
376    ///
377    /// A counter whose current value is zero.
378    #[inline]
379    fn default() -> Self {
380        Self::zero()
381    }
382}
383
384impl From<usize> for AtomicCount {
385    /// Converts an initial counter value into an [`AtomicCount`].
386    ///
387    /// # Parameters
388    ///
389    /// * `value` - The initial counter value.
390    ///
391    /// # Returns
392    ///
393    /// A counter initialized to `value`.
394    #[inline]
395    fn from(value: usize) -> Self {
396        Self::new(value)
397    }
398}
399
400impl fmt::Debug for AtomicCount {
401    /// Formats the current counter value for debugging.
402    ///
403    /// # Parameters
404    ///
405    /// * `f` - The formatter receiving the debug representation.
406    ///
407    /// # Returns
408    ///
409    /// A formatting result from the formatter.
410    #[inline]
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        f.debug_struct("AtomicCount")
413            .field("value", &self.get())
414            .finish()
415    }
416}
417
418impl fmt::Display for AtomicCount {
419    /// Formats the current counter value with decimal display formatting.
420    ///
421    /// # Parameters
422    ///
423    /// * `f` - The formatter receiving the displayed value.
424    ///
425    /// # Returns
426    ///
427    /// A formatting result from the formatter.
428    #[inline]
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        write!(f, "{}", self.get())
431    }
432}