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/// # Examples
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 /// # Examples
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 /// # Examples
86 ///
87 /// ```rust
88 /// use qubit_atomic::AtomicSignedCount;
89 ///
90 /// let counter = AtomicSignedCount::zero();
91 /// assert!(counter.is_zero());
92 /// ```
93 #[inline(always)]
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 /// # Examples
105 ///
106 /// ```rust
107 /// use qubit_atomic::AtomicSignedCount;
108 ///
109 /// let counter = AtomicSignedCount::new(-7);
110 /// assert_eq!(counter.get(), -7);
111 /// ```
112 #[must_use]
113 #[inline(always)]
114 pub fn get(&self) -> isize {
115 self.inner.load(Ordering::Acquire)
116 }
117
118 /// Returns whether the current counter value is zero.
119 ///
120 /// # Returns
121 ///
122 /// `true` if the current value is zero, otherwise `false`.
123 ///
124 /// # Examples
125 ///
126 /// ```rust
127 /// use qubit_atomic::AtomicSignedCount;
128 ///
129 /// let counter = AtomicSignedCount::zero();
130 /// assert!(counter.is_zero());
131 /// ```
132 #[must_use]
133 #[inline(always)]
134 pub fn is_zero(&self) -> bool {
135 self.get() == 0
136 }
137
138 /// Returns whether the current counter value is greater than zero.
139 ///
140 /// # Returns
141 ///
142 /// `true` if the current value is greater than zero, otherwise `false`.
143 ///
144 /// # Examples
145 ///
146 /// ```rust
147 /// use qubit_atomic::AtomicSignedCount;
148 ///
149 /// let counter = AtomicSignedCount::new(1);
150 /// assert!(counter.is_positive());
151 /// ```
152 #[must_use]
153 #[inline(always)]
154 pub fn is_positive(&self) -> bool {
155 self.get() > 0
156 }
157
158 /// Returns whether the current counter value is less than zero.
159 ///
160 /// # Returns
161 ///
162 /// `true` if the current value is less than zero, otherwise `false`.
163 ///
164 /// # Examples
165 ///
166 /// ```rust
167 /// use qubit_atomic::AtomicSignedCount;
168 ///
169 /// let counter = AtomicSignedCount::new(-1);
170 /// assert!(counter.is_negative());
171 /// ```
172 #[must_use]
173 #[inline(always)]
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 /// # Examples
189 ///
190 /// ```rust
191 /// use qubit_atomic::AtomicSignedCount;
192 ///
193 /// let counter = AtomicSignedCount::zero();
194 /// assert_eq!(counter.inc(), 1);
195 /// ```
196 #[inline(always)]
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 /// # Examples
212 ///
213 /// ```rust
214 /// use qubit_atomic::AtomicSignedCount;
215 ///
216 /// let counter = AtomicSignedCount::zero();
217 /// assert_eq!(counter.dec(), -1);
218 /// ```
219 #[inline(always)]
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 /// # Examples
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(always)]
247 pub fn add(&self, delta: isize) -> isize {
248 self.try_add(delta)
249 .expect("atomic signed counter out of range")
250 }
251
252 /// Tries to add `delta` to the counter.
253 ///
254 /// # Parameters
255 ///
256 /// * `delta` - The amount to add. It may be negative.
257 ///
258 /// # Returns
259 ///
260 /// `Some(new_value)` if the addition succeeds, or `None` if it would
261 /// overflow or underflow the signed range. On `None`, the counter is left
262 /// unchanged.
263 ///
264 /// # Examples
265 ///
266 /// ```rust
267 /// use qubit_atomic::AtomicSignedCount;
268 ///
269 /// let counter = AtomicSignedCount::new(-2);
270 /// assert_eq!(counter.try_add(5), Some(3));
271 /// ```
272 #[inline(always)]
273 pub fn try_add(&self, delta: isize) -> Option<isize> {
274 self.try_update(|current| current.checked_add(delta))
275 }
276
277 /// Subtracts `delta` from the counter and returns the new value.
278 ///
279 /// # Parameters
280 ///
281 /// * `delta` - The amount to subtract. It may be negative.
282 ///
283 /// # Returns
284 ///
285 /// The counter value after the subtraction.
286 ///
287 /// # Panics
288 ///
289 /// Panics if the subtraction would overflow or underflow the signed range.
290 ///
291 /// # Examples
292 ///
293 /// ```rust
294 /// use qubit_atomic::AtomicSignedCount;
295 ///
296 /// let counter = AtomicSignedCount::new(2);
297 /// assert_eq!(counter.sub(5), -3);
298 /// ```
299 #[inline(always)]
300 pub fn sub(&self, delta: isize) -> isize {
301 self.try_sub(delta)
302 .expect("atomic signed counter out of range")
303 }
304
305 /// Tries to subtract `delta` from the counter.
306 ///
307 /// # Parameters
308 ///
309 /// * `delta` - The amount to subtract. It may be negative.
310 ///
311 /// # Returns
312 ///
313 /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
314 /// overflow or underflow the signed range. On `None`, the counter is left
315 /// unchanged.
316 ///
317 /// # Examples
318 ///
319 /// ```rust
320 /// use qubit_atomic::AtomicSignedCount;
321 ///
322 /// let counter = AtomicSignedCount::new(2);
323 /// assert_eq!(counter.try_sub(5), Some(-3));
324 /// ```
325 #[inline(always)]
326 pub fn try_sub(&self, delta: isize) -> Option<isize> {
327 self.try_update(|current| current.checked_sub(delta))
328 }
329
330 /// Applies a checked update with synchronization semantics.
331 ///
332 /// # Parameters
333 ///
334 /// * `update` - A function that maps the current value to the next value,
335 /// or returns `None` to reject the update.
336 ///
337 /// # Returns
338 ///
339 /// `Some(new_value)` if the update succeeds, or `None` if `update`
340 /// rejects the current value. A rejected update leaves the counter
341 /// unchanged.
342 fn try_update<F>(&self, mut update: F) -> Option<isize>
343 where
344 F: FnMut(isize) -> Option<isize>,
345 {
346 let mut current = self.get();
347 loop {
348 let next = update(current)?;
349 match self.inner.compare_exchange_weak(
350 current,
351 next,
352 Ordering::AcqRel,
353 Ordering::Acquire,
354 ) {
355 Ok(_) => return Some(next),
356 Err(actual) => current = actual,
357 }
358 }
359 }
360}
361
362impl Default for AtomicSignedCount {
363 /// Creates a zero-valued signed atomic counter.
364 ///
365 /// # Returns
366 ///
367 /// A signed counter whose current value is zero.
368 #[inline(always)]
369 fn default() -> Self {
370 Self::zero()
371 }
372}
373
374impl From<isize> for AtomicSignedCount {
375 /// Converts an initial counter value into an [`AtomicSignedCount`].
376 ///
377 /// # Parameters
378 ///
379 /// * `value` - The initial counter value.
380 ///
381 /// # Returns
382 ///
383 /// A signed counter initialized to `value`.
384 #[inline(always)]
385 fn from(value: isize) -> Self {
386 Self::new(value)
387 }
388}
389
390impl fmt::Debug for AtomicSignedCount {
391 /// Formats the current counter value for debugging.
392 ///
393 /// # Parameters
394 ///
395 /// * `f` - The formatter receiving the debug representation.
396 ///
397 /// # Returns
398 ///
399 /// A formatting result from the formatter.
400 #[inline]
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 f.debug_struct("AtomicSignedCount")
403 .field("value", &self.get())
404 .finish()
405 }
406}
407
408impl fmt::Display for AtomicSignedCount {
409 /// Formats the current counter value with decimal display formatting.
410 ///
411 /// # Parameters
412 ///
413 /// * `f` - The formatter receiving the displayed value.
414 ///
415 /// # Returns
416 ///
417 /// A formatting result from the formatter.
418 #[inline]
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 write!(f, "{}", self.get())
421 }
422}