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