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/// # Example
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    /// # Example
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    /// # Example
86    ///
87    /// ```rust
88    /// use qubit_atomic::AtomicSignedCount;
89    ///
90    /// let counter = AtomicSignedCount::zero();
91    /// assert!(counter.is_zero());
92    /// ```
93    #[inline]
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    /// # Example
105    ///
106    /// ```rust
107    /// use qubit_atomic::AtomicSignedCount;
108    ///
109    /// let counter = AtomicSignedCount::new(-7);
110    /// assert_eq!(counter.get(), -7);
111    /// ```
112    #[inline]
113    pub fn get(&self) -> isize {
114        self.inner.load(Ordering::Acquire)
115    }
116
117    /// Returns whether the current counter value is zero.
118    ///
119    /// # Returns
120    ///
121    /// `true` if the current value is zero, otherwise `false`.
122    ///
123    /// # Example
124    ///
125    /// ```rust
126    /// use qubit_atomic::AtomicSignedCount;
127    ///
128    /// let counter = AtomicSignedCount::zero();
129    /// assert!(counter.is_zero());
130    /// ```
131    #[inline]
132    pub fn is_zero(&self) -> bool {
133        self.get() == 0
134    }
135
136    /// Returns whether the current counter value is greater than zero.
137    ///
138    /// # Returns
139    ///
140    /// `true` if the current value is greater than zero, otherwise `false`.
141    ///
142    /// # Example
143    ///
144    /// ```rust
145    /// use qubit_atomic::AtomicSignedCount;
146    ///
147    /// let counter = AtomicSignedCount::new(1);
148    /// assert!(counter.is_positive());
149    /// ```
150    #[inline]
151    pub fn is_positive(&self) -> bool {
152        self.get() > 0
153    }
154
155    /// Returns whether the current counter value is less than zero.
156    ///
157    /// # Returns
158    ///
159    /// `true` if the current value is less than zero, otherwise `false`.
160    ///
161    /// # Example
162    ///
163    /// ```rust
164    /// use qubit_atomic::AtomicSignedCount;
165    ///
166    /// let counter = AtomicSignedCount::new(-1);
167    /// assert!(counter.is_negative());
168    /// ```
169    #[inline]
170    pub fn is_negative(&self) -> bool {
171        self.get() < 0
172    }
173
174    /// Increments the counter by one and returns the new value.
175    ///
176    /// # Returns
177    ///
178    /// The counter value after the increment.
179    ///
180    /// # Panics
181    ///
182    /// Panics if the increment would overflow [`isize::MAX`].
183    ///
184    /// # Example
185    ///
186    /// ```rust
187    /// use qubit_atomic::AtomicSignedCount;
188    ///
189    /// let counter = AtomicSignedCount::zero();
190    /// assert_eq!(counter.inc(), 1);
191    /// ```
192    #[inline]
193    pub fn inc(&self) -> isize {
194        self.add(1)
195    }
196
197    /// Decrements the counter by one and returns the new value.
198    ///
199    /// # Returns
200    ///
201    /// The counter value after the decrement.
202    ///
203    /// # Panics
204    ///
205    /// Panics if the decrement would underflow [`isize::MIN`].
206    ///
207    /// # Example
208    ///
209    /// ```rust
210    /// use qubit_atomic::AtomicSignedCount;
211    ///
212    /// let counter = AtomicSignedCount::zero();
213    /// assert_eq!(counter.dec(), -1);
214    /// ```
215    #[inline]
216    pub fn dec(&self) -> isize {
217        self.sub(1)
218    }
219
220    /// Adds `delta` to the counter and returns the new value.
221    ///
222    /// # Parameters
223    ///
224    /// * `delta` - The amount to add. It may be negative.
225    ///
226    /// # Returns
227    ///
228    /// The counter value after the addition.
229    ///
230    /// # Panics
231    ///
232    /// Panics if the addition would overflow or underflow the signed range.
233    ///
234    /// # Example
235    ///
236    /// ```rust
237    /// use qubit_atomic::AtomicSignedCount;
238    ///
239    /// let counter = AtomicSignedCount::new(2);
240    /// assert_eq!(counter.add(-5), -3);
241    /// ```
242    #[inline]
243    pub fn add(&self, delta: isize) -> isize {
244        self.try_add(delta)
245            .expect("atomic signed counter out of range")
246    }
247
248    /// Tries to add `delta` to the counter.
249    ///
250    /// # Parameters
251    ///
252    /// * `delta` - The amount to add. It may be negative.
253    ///
254    /// # Returns
255    ///
256    /// `Some(new_value)` if the addition succeeds, or `None` if it would
257    /// overflow or underflow the signed range. On `None`, the counter is left
258    /// unchanged.
259    ///
260    /// # Example
261    ///
262    /// ```rust
263    /// use qubit_atomic::AtomicSignedCount;
264    ///
265    /// let counter = AtomicSignedCount::new(-2);
266    /// assert_eq!(counter.try_add(5), Some(3));
267    /// ```
268    #[inline]
269    pub fn try_add(&self, delta: isize) -> Option<isize> {
270        self.try_update(|current| current.checked_add(delta))
271    }
272
273    /// Subtracts `delta` from the counter and returns the new value.
274    ///
275    /// # Parameters
276    ///
277    /// * `delta` - The amount to subtract. It may be negative.
278    ///
279    /// # Returns
280    ///
281    /// The counter value after the subtraction.
282    ///
283    /// # Panics
284    ///
285    /// Panics if the subtraction would overflow or underflow the signed range.
286    ///
287    /// # Example
288    ///
289    /// ```rust
290    /// use qubit_atomic::AtomicSignedCount;
291    ///
292    /// let counter = AtomicSignedCount::new(2);
293    /// assert_eq!(counter.sub(5), -3);
294    /// ```
295    #[inline]
296    pub fn sub(&self, delta: isize) -> isize {
297        self.try_sub(delta)
298            .expect("atomic signed counter out of range")
299    }
300
301    /// Tries to subtract `delta` from the counter.
302    ///
303    /// # Parameters
304    ///
305    /// * `delta` - The amount to subtract. It may be negative.
306    ///
307    /// # Returns
308    ///
309    /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
310    /// overflow or underflow the signed range. On `None`, the counter is left
311    /// unchanged.
312    ///
313    /// # Example
314    ///
315    /// ```rust
316    /// use qubit_atomic::AtomicSignedCount;
317    ///
318    /// let counter = AtomicSignedCount::new(2);
319    /// assert_eq!(counter.try_sub(5), Some(-3));
320    /// ```
321    #[inline]
322    pub fn try_sub(&self, delta: isize) -> Option<isize> {
323        self.try_update(|current| current.checked_sub(delta))
324    }
325
326    /// Applies a checked update with synchronization semantics.
327    ///
328    /// # Parameters
329    ///
330    /// * `update` - A function that maps the current value to the next value,
331    ///   or returns `None` to reject the update.
332    ///
333    /// # Returns
334    ///
335    /// `Some(new_value)` if the update succeeds, or `None` if `update`
336    /// rejects the current value. A rejected update leaves the counter
337    /// unchanged.
338    #[inline]
339    fn try_update<F>(&self, mut update: F) -> Option<isize>
340    where
341        F: FnMut(isize) -> Option<isize>,
342    {
343        let mut current = self.get();
344        loop {
345            let next = update(current)?;
346            match self.inner.compare_exchange_weak(
347                current,
348                next,
349                Ordering::AcqRel,
350                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")
400            .field("value", &self.get())
401            .finish()
402    }
403}
404
405impl fmt::Display for AtomicSignedCount {
406    /// Formats the current counter value with decimal display formatting.
407    ///
408    /// # Parameters
409    ///
410    /// * `f` - The formatter receiving the displayed value.
411    ///
412    /// # Returns
413    ///
414    /// A formatting result from the formatter.
415    #[inline]
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        write!(f, "{}", self.get())
418    }
419}