Skip to main content

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