Skip to main content

qubit_atomic/atomic/
atomic_count.rs

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