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}