qubit_atomic/atomic/atomic_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 Count
10//!
11//! Provides a non-negative atomic counter for values whose transitions are
12//! used as synchronization signals.
13
14use std::fmt;
15use std::sync::atomic::{
16 AtomicUsize as StdAtomicUsize,
17 Ordering,
18};
19
20/// Applies a checked update through the production weak-CAS retry loop.
21///
22/// This hidden test bridge lets Loom supply instrumented atomic operations
23/// while exercising the same retry core used by [`AtomicCount`].
24///
25/// # Parameters
26///
27/// * `load_acquire` - Loads the current value with acquire ordering.
28/// * `compare_exchange_weak_acqrel_acquire` - Performs weak CAS with
29/// acquire-release success ordering and acquire failure ordering.
30/// * `update` - Maps the observed value to a proposed next value, or returns
31/// `None` to reject the update.
32///
33/// # Returns
34///
35/// `Some(new_value)` after a successful update, or `None` when `update`
36/// rejects the observed value. The update and CAS callbacks may run more than
37/// once when a weak CAS fails.
38#[doc(hidden)]
39#[inline]
40pub fn try_update_atomic_count<L, C, F>(
41 mut load_acquire: L,
42 mut compare_exchange_weak_acqrel_acquire: C,
43 mut update: F,
44) -> Option<usize>
45where
46 L: FnMut() -> usize,
47 C: FnMut(usize, usize) -> Result<usize, usize>,
48 F: FnMut(usize) -> Option<usize>,
49{
50 let mut current = load_acquire();
51 loop {
52 let next = update(current)?;
53 match compare_exchange_weak_acqrel_acquire(current, next) {
54 Ok(_) => return Some(next),
55 Err(actual) => current = actual,
56 }
57 }
58}
59
60/// A non-negative atomic counter with synchronization-oriented operations.
61///
62/// Use this type when the counter value is part of a concurrent state machine,
63/// such as active task counts, in-flight request counts, resource usage counts,
64/// or shutdown and termination checks. Its operations return the value after
65/// the update, which makes zero-transition logic straightforward.
66///
67/// For pure metrics, statistics, event counters, or ID generation, prefer the
68/// regular atomic integer types such as [`Atomic<usize>`](crate::Atomic).
69/// Those types keep arithmetic operations lightweight for pure counting.
70///
71/// This counter never wraps. Incrementing past [`usize::MAX`] panics, and
72/// decrementing below zero panics. Use [`try_add`](Self::try_add),
73/// [`try_dec`](Self::try_dec), or [`try_sub`](Self::try_sub) when overflow or
74/// underflow is a normal business outcome.
75///
76/// # Example
77///
78/// ```rust
79/// use qubit_atomic::AtomicCount;
80///
81/// let active_tasks = AtomicCount::zero();
82///
83/// active_tasks.inc();
84/// assert!(!active_tasks.is_zero());
85///
86/// if active_tasks.dec() == 0 {
87/// // The last active task finished; notify termination waiters here.
88/// }
89/// ```
90#[repr(transparent)]
91pub struct AtomicCount {
92 /// Standard-library atomic storage for the non-negative counter value.
93 inner: StdAtomicUsize,
94}
95
96impl AtomicCount {
97 /// Creates a new non-negative atomic counter.
98 ///
99 /// # Parameters
100 ///
101 /// * `value` - The initial counter value.
102 ///
103 /// # Returns
104 ///
105 /// A counter initialized to `value`.
106 ///
107 /// # Example
108 ///
109 /// ```rust
110 /// use qubit_atomic::AtomicCount;
111 ///
112 /// let counter = AtomicCount::new(3);
113 /// assert_eq!(counter.get(), 3);
114 /// ```
115 #[inline]
116 pub const fn new(value: usize) -> Self {
117 Self {
118 inner: StdAtomicUsize::new(value),
119 }
120 }
121
122 /// Creates a new counter initialized to zero.
123 ///
124 /// # Returns
125 ///
126 /// A counter whose current value is zero.
127 ///
128 /// # Example
129 ///
130 /// ```rust
131 /// use qubit_atomic::AtomicCount;
132 ///
133 /// let counter = AtomicCount::zero();
134 /// assert!(counter.is_zero());
135 /// ```
136 #[inline]
137 pub const fn zero() -> Self {
138 Self::new(0)
139 }
140
141 /// Gets the current counter value.
142 ///
143 /// # Returns
144 ///
145 /// The current counter value.
146 ///
147 /// # Example
148 ///
149 /// ```rust
150 /// use qubit_atomic::AtomicCount;
151 ///
152 /// let counter = AtomicCount::new(7);
153 /// assert_eq!(counter.get(), 7);
154 /// ```
155 #[inline]
156 pub fn get(&self) -> usize {
157 self.inner.load(Ordering::Acquire)
158 }
159
160 /// Returns whether the current counter value is zero.
161 ///
162 /// # Returns
163 ///
164 /// `true` if the current value is zero, otherwise `false`.
165 ///
166 /// # Example
167 ///
168 /// ```rust
169 /// use qubit_atomic::AtomicCount;
170 ///
171 /// let counter = AtomicCount::zero();
172 /// assert!(counter.is_zero());
173 /// ```
174 #[inline]
175 pub fn is_zero(&self) -> bool {
176 self.get() == 0
177 }
178
179 /// Returns whether the current counter value is greater than zero.
180 ///
181 /// # Returns
182 ///
183 /// `true` if the current value is greater than zero, otherwise `false`.
184 ///
185 /// # Example
186 ///
187 /// ```rust
188 /// use qubit_atomic::AtomicCount;
189 ///
190 /// let counter = AtomicCount::new(1);
191 /// assert!(counter.is_positive());
192 /// ```
193 #[inline]
194 pub fn is_positive(&self) -> bool {
195 self.get() > 0
196 }
197
198 /// Increments the counter by one and returns the new value.
199 ///
200 /// # Returns
201 ///
202 /// The counter value after the increment.
203 ///
204 /// # Panics
205 ///
206 /// Panics if the increment would overflow [`usize::MAX`].
207 ///
208 /// # Example
209 ///
210 /// ```rust
211 /// use qubit_atomic::AtomicCount;
212 ///
213 /// let counter = AtomicCount::zero();
214 /// assert_eq!(counter.inc(), 1);
215 /// ```
216 #[inline]
217 pub fn inc(&self) -> usize {
218 self.add(1)
219 }
220
221 /// Adds `delta` to the counter and returns the new value.
222 ///
223 /// # Parameters
224 ///
225 /// * `delta` - The amount to add.
226 ///
227 /// # Returns
228 ///
229 /// The counter value after the addition.
230 ///
231 /// # Panics
232 ///
233 /// Panics if the addition would overflow [`usize::MAX`].
234 ///
235 /// # Example
236 ///
237 /// ```rust
238 /// use qubit_atomic::AtomicCount;
239 ///
240 /// let counter = AtomicCount::new(2);
241 /// assert_eq!(counter.add(3), 5);
242 /// ```
243 #[inline]
244 pub fn add(&self, delta: usize) -> usize {
245 self.try_add(delta).expect("atomic counter overflow")
246 }
247
248 /// Tries to add `delta` to the counter.
249 ///
250 /// # Parameters
251 ///
252 /// * `delta` - The amount to add.
253 ///
254 /// # Returns
255 ///
256 /// `Some(new_value)` if the addition succeeds, or `None` if it would
257 /// overflow [`usize::MAX`]. On `None`, the counter is left unchanged.
258 ///
259 /// # Example
260 ///
261 /// ```rust
262 /// use qubit_atomic::AtomicCount;
263 ///
264 /// let counter = AtomicCount::new(2);
265 /// assert_eq!(counter.try_add(3), Some(5));
266 /// ```
267 #[inline]
268 pub fn try_add(&self, delta: usize) -> Option<usize> {
269 self.try_update(|current| current.checked_add(delta))
270 }
271
272 /// Decrements the counter by one and returns the new value.
273 ///
274 /// This method is useful for detecting the transition to zero:
275 ///
276 /// ```rust
277 /// use qubit_atomic::AtomicCount;
278 ///
279 /// let counter = AtomicCount::new(1);
280 /// if counter.dec() == 0 {
281 /// // This call consumed the final counted item.
282 /// }
283 /// ```
284 ///
285 /// # Returns
286 ///
287 /// The counter value after the decrement.
288 ///
289 /// # Panics
290 ///
291 /// Panics if the current value is zero.
292 #[inline]
293 pub fn dec(&self) -> usize {
294 self.try_dec().expect("atomic counter underflow")
295 }
296
297 /// Tries to decrement the counter by one.
298 ///
299 /// # Returns
300 ///
301 /// `Some(new_value)` if the decrement succeeds, or `None` if the current
302 /// value is zero. On `None`, the counter is left unchanged.
303 ///
304 /// # Example
305 ///
306 /// ```rust
307 /// use qubit_atomic::AtomicCount;
308 ///
309 /// let counter = AtomicCount::new(1);
310 /// assert_eq!(counter.try_dec(), Some(0));
311 /// assert_eq!(counter.try_dec(), None);
312 /// ```
313 #[inline]
314 pub fn try_dec(&self) -> Option<usize> {
315 self.try_sub(1)
316 }
317
318 /// Subtracts `delta` from the counter and returns the new value.
319 ///
320 /// # Parameters
321 ///
322 /// * `delta` - The amount to subtract.
323 ///
324 /// # Returns
325 ///
326 /// The counter value after the subtraction.
327 ///
328 /// # Panics
329 ///
330 /// Panics if the subtraction would make the counter negative.
331 ///
332 /// # Example
333 ///
334 /// ```rust
335 /// use qubit_atomic::AtomicCount;
336 ///
337 /// let counter = AtomicCount::new(5);
338 /// assert_eq!(counter.sub(2), 3);
339 /// ```
340 #[inline]
341 pub fn sub(&self, delta: usize) -> usize {
342 self.try_sub(delta).expect("atomic counter underflow")
343 }
344
345 /// Tries to subtract `delta` from the counter.
346 ///
347 /// # Parameters
348 ///
349 /// * `delta` - The amount to subtract.
350 ///
351 /// # Returns
352 ///
353 /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
354 /// make the counter negative. On `None`, the counter is left unchanged.
355 ///
356 /// # Example
357 ///
358 /// ```rust
359 /// use qubit_atomic::AtomicCount;
360 ///
361 /// let counter = AtomicCount::new(3);
362 /// assert_eq!(counter.try_sub(2), Some(1));
363 /// assert_eq!(counter.try_sub(2), None);
364 /// ```
365 #[inline]
366 pub fn try_sub(&self, delta: usize) -> Option<usize> {
367 self.try_update(|current| current.checked_sub(delta))
368 }
369
370 /// Applies a checked update with synchronization semantics.
371 ///
372 /// # Parameters
373 ///
374 /// * `update` - A function that maps the current value to the next value,
375 /// or returns `None` to reject the update.
376 ///
377 /// # Returns
378 ///
379 /// `Some(new_value)` if the update succeeds, or `None` if `update`
380 /// rejects the current value. A rejected update leaves the counter
381 /// unchanged.
382 #[inline]
383 fn try_update<F>(&self, update: F) -> Option<usize>
384 where
385 F: FnMut(usize) -> Option<usize>,
386 {
387 try_update_atomic_count(
388 || self.inner.load(Ordering::Acquire),
389 |current, next| {
390 self.inner.compare_exchange_weak(
391 current,
392 next,
393 Ordering::AcqRel,
394 Ordering::Acquire,
395 )
396 },
397 update,
398 )
399 }
400}
401
402impl Default for AtomicCount {
403 /// Creates a zero-valued atomic counter.
404 ///
405 /// # Returns
406 ///
407 /// A counter whose current value is zero.
408 #[inline]
409 fn default() -> Self {
410 Self::zero()
411 }
412}
413
414impl From<usize> for AtomicCount {
415 /// Converts an initial counter value into an [`AtomicCount`].
416 ///
417 /// # Parameters
418 ///
419 /// * `value` - The initial counter value.
420 ///
421 /// # Returns
422 ///
423 /// A counter initialized to `value`.
424 #[inline]
425 fn from(value: usize) -> Self {
426 Self::new(value)
427 }
428}
429
430impl fmt::Debug for AtomicCount {
431 /// Formats the current counter value for debugging.
432 ///
433 /// # Parameters
434 ///
435 /// * `f` - The formatter receiving the debug representation.
436 ///
437 /// # Returns
438 ///
439 /// A formatting result from the formatter.
440 #[inline]
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 f.debug_struct("AtomicCount")
443 .field("value", &self.get())
444 .finish()
445 }
446}
447
448impl fmt::Display for AtomicCount {
449 /// Formats the current counter value with decimal display formatting.
450 ///
451 /// # Parameters
452 ///
453 /// * `f` - The formatter receiving the displayed value.
454 ///
455 /// # Returns
456 ///
457 /// A formatting result from the formatter.
458 #[inline]
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 write!(f, "{}", self.get())
461 }
462}