Skip to main content

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