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}