Skip to main content

qubit_atomic/atomic/
atomic_counter.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9
10//! # Atomic Counter
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 [`AtomicUsize`](crate::AtomicUsize).
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::AtomicCounter;
45///
46/// let active_tasks = AtomicCounter::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 AtomicCounter {
61    inner: StdAtomicUsize,
62}
63
64impl AtomicCounter {
65    /// Creates a new non-negative atomic counter.
66    ///
67    /// # Parameters
68    ///
69    /// * `value` - The initial counter value.
70    ///
71    /// # Returns
72    ///
73    /// A counter initialized to `value`.
74    ///
75    /// # Example
76    ///
77    /// ```rust
78    /// use qubit_atomic::AtomicCounter;
79    ///
80    /// let counter = AtomicCounter::new(3);
81    /// assert_eq!(counter.get(), 3);
82    /// ```
83    #[inline]
84    pub const fn new(value: usize) -> Self {
85        Self {
86            inner: StdAtomicUsize::new(value),
87        }
88    }
89
90    /// Creates a new counter initialized to zero.
91    ///
92    /// # Returns
93    ///
94    /// A counter whose current value is zero.
95    ///
96    /// # Example
97    ///
98    /// ```rust
99    /// use qubit_atomic::AtomicCounter;
100    ///
101    /// let counter = AtomicCounter::zero();
102    /// assert!(counter.is_zero());
103    /// ```
104    #[inline]
105    pub const fn zero() -> Self {
106        Self::new(0)
107    }
108
109    /// Gets the current counter value.
110    ///
111    /// # Returns
112    ///
113    /// The current counter value.
114    ///
115    /// # Example
116    ///
117    /// ```rust
118    /// use qubit_atomic::AtomicCounter;
119    ///
120    /// let counter = AtomicCounter::new(7);
121    /// assert_eq!(counter.get(), 7);
122    /// ```
123    #[inline]
124    pub fn get(&self) -> usize {
125        self.inner.load(Ordering::Acquire)
126    }
127
128    /// Returns whether the current counter value is zero.
129    ///
130    /// # Returns
131    ///
132    /// `true` if the current value is zero, otherwise `false`.
133    ///
134    /// # Example
135    ///
136    /// ```rust
137    /// use qubit_atomic::AtomicCounter;
138    ///
139    /// let counter = AtomicCounter::zero();
140    /// assert!(counter.is_zero());
141    /// ```
142    #[inline]
143    pub fn is_zero(&self) -> bool {
144        self.get() == 0
145    }
146
147    /// Returns whether the current counter value is greater than zero.
148    ///
149    /// # Returns
150    ///
151    /// `true` if the current value is greater than zero, otherwise `false`.
152    ///
153    /// # Example
154    ///
155    /// ```rust
156    /// use qubit_atomic::AtomicCounter;
157    ///
158    /// let counter = AtomicCounter::new(1);
159    /// assert!(counter.is_positive());
160    /// ```
161    #[inline]
162    pub fn is_positive(&self) -> bool {
163        self.get() > 0
164    }
165
166    /// Increments the counter by one and returns the new value.
167    ///
168    /// # Returns
169    ///
170    /// The counter value after the increment.
171    ///
172    /// # Panics
173    ///
174    /// Panics if the increment would overflow [`usize::MAX`].
175    ///
176    /// # Example
177    ///
178    /// ```rust
179    /// use qubit_atomic::AtomicCounter;
180    ///
181    /// let counter = AtomicCounter::zero();
182    /// assert_eq!(counter.inc(), 1);
183    /// ```
184    #[inline]
185    pub fn inc(&self) -> usize {
186        self.add(1)
187    }
188
189    /// Adds `delta` to the counter and returns the new value.
190    ///
191    /// # Parameters
192    ///
193    /// * `delta` - The amount to add.
194    ///
195    /// # Returns
196    ///
197    /// The counter value after the addition.
198    ///
199    /// # Panics
200    ///
201    /// Panics if the addition would overflow [`usize::MAX`].
202    ///
203    /// # Example
204    ///
205    /// ```rust
206    /// use qubit_atomic::AtomicCounter;
207    ///
208    /// let counter = AtomicCounter::new(2);
209    /// assert_eq!(counter.add(3), 5);
210    /// ```
211    #[inline]
212    pub fn add(&self, delta: usize) -> usize {
213        self.try_add(delta).expect("atomic counter overflow")
214    }
215
216    /// Tries to add `delta` to the counter.
217    ///
218    /// # Parameters
219    ///
220    /// * `delta` - The amount to add.
221    ///
222    /// # Returns
223    ///
224    /// `Some(new_value)` if the addition succeeds, or `None` if it would
225    /// overflow [`usize::MAX`]. On `None`, the counter is left unchanged.
226    ///
227    /// # Example
228    ///
229    /// ```rust
230    /// use qubit_atomic::AtomicCounter;
231    ///
232    /// let counter = AtomicCounter::new(2);
233    /// assert_eq!(counter.try_add(3), Some(5));
234    /// ```
235    #[inline]
236    pub fn try_add(&self, delta: usize) -> Option<usize> {
237        self.try_update(|current| current.checked_add(delta))
238    }
239
240    /// Decrements the counter by one and returns the new value.
241    ///
242    /// This method is useful for detecting the transition to zero:
243    ///
244    /// ```rust
245    /// use qubit_atomic::AtomicCounter;
246    ///
247    /// let counter = AtomicCounter::new(1);
248    /// if counter.dec() == 0 {
249    ///     // This call consumed the final counted item.
250    /// }
251    /// ```
252    ///
253    /// # Returns
254    ///
255    /// The counter value after the decrement.
256    ///
257    /// # Panics
258    ///
259    /// Panics if the current value is zero.
260    #[inline]
261    pub fn dec(&self) -> usize {
262        self.try_dec().expect("atomic counter underflow")
263    }
264
265    /// Tries to decrement the counter by one.
266    ///
267    /// # Returns
268    ///
269    /// `Some(new_value)` if the decrement succeeds, or `None` if the current
270    /// value is zero. On `None`, the counter is left unchanged.
271    ///
272    /// # Example
273    ///
274    /// ```rust
275    /// use qubit_atomic::AtomicCounter;
276    ///
277    /// let counter = AtomicCounter::new(1);
278    /// assert_eq!(counter.try_dec(), Some(0));
279    /// assert_eq!(counter.try_dec(), None);
280    /// ```
281    #[inline]
282    pub fn try_dec(&self) -> Option<usize> {
283        self.try_sub(1)
284    }
285
286    /// Subtracts `delta` from the counter and returns the new value.
287    ///
288    /// # Parameters
289    ///
290    /// * `delta` - The amount to subtract.
291    ///
292    /// # Returns
293    ///
294    /// The counter value after the subtraction.
295    ///
296    /// # Panics
297    ///
298    /// Panics if the subtraction would make the counter negative.
299    ///
300    /// # Example
301    ///
302    /// ```rust
303    /// use qubit_atomic::AtomicCounter;
304    ///
305    /// let counter = AtomicCounter::new(5);
306    /// assert_eq!(counter.sub(2), 3);
307    /// ```
308    #[inline]
309    pub fn sub(&self, delta: usize) -> usize {
310        self.try_sub(delta).expect("atomic counter underflow")
311    }
312
313    /// Tries to subtract `delta` from the counter.
314    ///
315    /// # Parameters
316    ///
317    /// * `delta` - The amount to subtract.
318    ///
319    /// # Returns
320    ///
321    /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
322    /// make the counter negative. On `None`, the counter is left unchanged.
323    ///
324    /// # Example
325    ///
326    /// ```rust
327    /// use qubit_atomic::AtomicCounter;
328    ///
329    /// let counter = AtomicCounter::new(3);
330    /// assert_eq!(counter.try_sub(2), Some(1));
331    /// assert_eq!(counter.try_sub(2), None);
332    /// ```
333    #[inline]
334    pub fn try_sub(&self, delta: usize) -> Option<usize> {
335        self.try_update(|current| current.checked_sub(delta))
336    }
337
338    /// Applies a checked update with synchronization semantics.
339    ///
340    /// # Parameters
341    ///
342    /// * `update` - A function that maps the current value to the next value,
343    ///   or returns `None` to reject the update.
344    ///
345    /// # Returns
346    ///
347    /// `Some(new_value)` if the update succeeds, or `None` if `update`
348    /// rejects the current value. A rejected update leaves the counter
349    /// unchanged.
350    #[inline]
351    fn try_update<F>(&self, update: F) -> Option<usize>
352    where
353        F: Fn(usize) -> Option<usize>,
354    {
355        let mut current = self.get();
356        loop {
357            let next = update(current)?;
358            match self.inner.compare_exchange_weak(
359                current,
360                next,
361                Ordering::AcqRel,
362                Ordering::Acquire,
363            ) {
364                Ok(_) => return Some(next),
365                Err(actual) => current = actual,
366            }
367        }
368    }
369}
370
371impl Default for AtomicCounter {
372    #[inline]
373    fn default() -> Self {
374        Self::zero()
375    }
376}
377
378impl From<usize> for AtomicCounter {
379    #[inline]
380    fn from(value: usize) -> Self {
381        Self::new(value)
382    }
383}
384
385impl fmt::Debug for AtomicCounter {
386    #[inline]
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        f.debug_struct("AtomicCounter")
389            .field("value", &self.get())
390            .finish()
391    }
392}
393
394impl fmt::Display for AtomicCounter {
395    #[inline]
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        write!(f, "{}", self.get())
398    }
399}