Skip to main content

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