Skip to main content

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