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)
254            .expect("atomic signed counter out of range")
255    }
256
257    /// Tries to add `delta` to the counter.
258    ///
259    /// # Parameters
260    ///
261    /// * `delta` - The amount to add. It may be negative.
262    ///
263    /// # Returns
264    ///
265    /// `Some(new_value)` if the addition succeeds, or `None` if it would
266    /// overflow or underflow the signed range. On `None`, the counter is left
267    /// unchanged.
268    ///
269    /// # Example
270    ///
271    /// ```rust
272    /// use qubit_atomic::AtomicSignedCount;
273    ///
274    /// let counter = AtomicSignedCount::new(-2);
275    /// assert_eq!(counter.try_add(5), Some(3));
276    /// ```
277    #[inline]
278    pub fn try_add(&self, delta: isize) -> Option<isize> {
279        self.try_update(|current| current.checked_add(delta))
280    }
281
282    /// Subtracts `delta` from the counter and returns the new value.
283    ///
284    /// # Parameters
285    ///
286    /// * `delta` - The amount to subtract. It may be negative.
287    ///
288    /// # Returns
289    ///
290    /// The counter value after the subtraction.
291    ///
292    /// # Panics
293    ///
294    /// Panics if the subtraction would overflow or underflow the signed range.
295    ///
296    /// # Example
297    ///
298    /// ```rust
299    /// use qubit_atomic::AtomicSignedCount;
300    ///
301    /// let counter = AtomicSignedCount::new(2);
302    /// assert_eq!(counter.sub(5), -3);
303    /// ```
304    #[inline]
305    pub fn sub(&self, delta: isize) -> isize {
306        self.try_sub(delta)
307            .expect("atomic signed counter out of range")
308    }
309
310    /// Tries to subtract `delta` from the counter.
311    ///
312    /// # Parameters
313    ///
314    /// * `delta` - The amount to subtract. It may be negative.
315    ///
316    /// # Returns
317    ///
318    /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
319    /// overflow or underflow the signed range. On `None`, the counter is left
320    /// unchanged.
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// use qubit_atomic::AtomicSignedCount;
326    ///
327    /// let counter = AtomicSignedCount::new(2);
328    /// assert_eq!(counter.try_sub(5), Some(-3));
329    /// ```
330    #[inline]
331    pub fn try_sub(&self, delta: isize) -> Option<isize> {
332        self.try_update(|current| current.checked_sub(delta))
333    }
334
335    /// Applies a checked update with synchronization semantics.
336    ///
337    /// # Parameters
338    ///
339    /// * `update` - A function that maps the current value to the next value,
340    ///   or returns `None` to reject the update.
341    ///
342    /// # Returns
343    ///
344    /// `Some(new_value)` if the update succeeds, or `None` if `update`
345    /// rejects the current value. A rejected update leaves the counter
346    /// unchanged.
347    #[inline]
348    fn try_update<F>(&self, update: F) -> Option<isize>
349    where
350        F: Fn(isize) -> Option<isize>,
351    {
352        let mut current = self.get();
353        loop {
354            let next = update(current)?;
355            match self.inner.compare_exchange_weak(
356                current,
357                next,
358                Ordering::AcqRel,
359                Ordering::Acquire,
360            ) {
361                Ok(_) => return Some(next),
362                Err(actual) => current = actual,
363            }
364        }
365    }
366}
367
368impl Default for AtomicSignedCount {
369    /// Creates a zero-valued signed atomic counter.
370    ///
371    /// # Returns
372    ///
373    /// A signed counter whose current value is zero.
374    #[inline]
375    fn default() -> Self {
376        Self::zero()
377    }
378}
379
380impl From<isize> for AtomicSignedCount {
381    /// Converts an initial counter value into an [`AtomicSignedCount`].
382    ///
383    /// # Parameters
384    ///
385    /// * `value` - The initial counter value.
386    ///
387    /// # Returns
388    ///
389    /// A signed counter initialized to `value`.
390    #[inline]
391    fn from(value: isize) -> Self {
392        Self::new(value)
393    }
394}
395
396impl fmt::Debug for AtomicSignedCount {
397    /// Formats the current counter value for debugging.
398    ///
399    /// # Parameters
400    ///
401    /// * `f` - The formatter receiving the debug representation.
402    ///
403    /// # Returns
404    ///
405    /// A formatting result from the formatter.
406    #[inline]
407    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408        f.debug_struct("AtomicSignedCount")
409            .field("value", &self.get())
410            .finish()
411    }
412}
413
414impl fmt::Display for AtomicSignedCount {
415    /// Formats the current counter value with decimal display formatting.
416    ///
417    /// # Parameters
418    ///
419    /// * `f` - The formatter receiving the displayed value.
420    ///
421    /// # Returns
422    ///
423    /// A formatting result from the formatter.
424    #[inline]
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        write!(f, "{}", self.get())
427    }
428}