Skip to main content

qubit_atomic/atomic/
atomic_count.rs

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