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}