qubit-atomic

User-friendly atomic operations wrapper providing JDK-like atomic API

Rust CI Coverage Crates.io Rust License

User-friendly atomic operations wrapper providing JDK-like atomic API for Rust.

Overview

Qubit Atomic is a comprehensive atomic operations library that provides easy-to-use atomic types with reasonable default memory orderings, similar to Java's java.util.concurrent.atomic package. It hides the complexity of memory ordering while maintaining zero-cost abstraction and allowing advanced users to access underlying types for fine-grained control.

Design Goals

Features

πŸ”’ Generic Atomic Primitive Types

πŸ”’ AtomicCount and AtomicSignedCount

πŸ”— Atomic Reference Type

🀝 Shared-Owner Convenience Wrappers

🎯 Focused Public API

Installation

Add this to your Cargo.toml:

[dependencies]
qubit-atomic = "0.13"

Quick Start

Specifying the value type T

Atomic<T> is generic over the primitive value type. Rust usually infers T from the argument to Atomic::new, but literals such as 0 can be ambiguous across integer widths.

In those cases, pick T explicitly using a turbofish on the constructor, or by annotating the variable:

use qubit_atomic::Atomic;

let wide: Atomic<u64> = Atomic::new(0);
assert_eq!(wide.load(), 0u64);

let narrow = Atomic::<i16>::new(0);
assert_eq!(narrow.load(), 0i16);

Const initialization

Use Atomic<T> for normal code. The generic constructor is the intended entry point because it keeps the public API compact and lets one type cover all primitive specializations.

Rust stable does not currently allow Atomic<T>::new to call the hidden trait constructor in a const fn. When you need a static or another const-initialized atomic value, use the concrete wrappers under atomic::primitive:

use qubit_atomic::atomic::primitive::{
    AtomicBool,
    AtomicU32,
};

static READY: AtomicBool = AtomicBool::new(false);
static NEXT_ID: AtomicU32 = AtomicU32::new(1);

Example: concurrent Atomic<i32>

use qubit_atomic::Atomic;
use std::sync::Arc;
use std::thread;

fn main() {
    let counter = Arc::new(Atomic::<i32>::new(0));
    let mut handles = vec![];

    // Spawn 10 threads, each increments counter 1000 times
    for _ in 0..10 {
        let counter = counter.clone();
        let handle = thread::spawn(move || {
            for _ in 0..1000 {
                counter.fetch_inc();
            }
        });
        handles.push(handle);
    }

    // Wait for all threads to complete
    for handle in handles {
        handle.join().unwrap();
    }

    // Verify result
    assert_eq!(counter.load(), 10000);
    println!("Final count: {}", counter.load());
}

AtomicCount and AtomicSignedCount

Use Atomic<T> for pure metrics. Use AtomicCount when the count is part of concurrent state, such as active work or termination checks.

use qubit_atomic::{
    AtomicCount,
    AtomicSignedCount,
};

fn main() {
    let active_tasks = AtomicCount::zero();

    active_tasks.inc();
    assert!(!active_tasks.is_zero());

    if active_tasks.dec() == 0 {
        println!("all active tasks are finished");
    }

    let backlog_delta = AtomicSignedCount::zero();
    assert_eq!(backlog_delta.add(5), 5);
    assert_eq!(backlog_delta.sub(8), -3);
    assert!(backlog_delta.is_negative());
}

Shared-owner wrappers

Use the ArcAtomic* wrappers when the atomic container itself is shared across threads or components. Their clone() operation clones the outer Arc, so all clones observe and update the same container.

use qubit_atomic::{
    ArcAtomic,
    ArcAtomicCount,
    ArcAtomicRef,
    ArcAtomicSignedCount,
};
use std::sync::Arc;
use std::thread;

fn main() {
    let requests = ArcAtomic::new(0usize);
    let worker_requests = requests.clone();

    let handle = thread::spawn(move || {
        worker_requests.fetch_inc();
    });
    handle.join().expect("worker should finish");

    assert_eq!(requests.load(), 1);
    assert_eq!(requests.strong_count(), 1);

    let active_tasks = ArcAtomicCount::zero();
    let shared_tasks = active_tasks.clone();
    assert_eq!(shared_tasks.inc(), 1);
    assert_eq!(active_tasks.get(), 1);

    let backlog = ArcAtomicSignedCount::zero();
    let shared_backlog = backlog.clone();
    assert_eq!(shared_backlog.sub(3), -3);
    assert_eq!(backlog.get(), -3);

    let config = ArcAtomicRef::from_value(String::from("v1"));
    let same_config = config.clone();
    same_config.store(Arc::new(String::from("v2")));
    assert_eq!(config.load().as_str(), "v2");
}

CAS Loop

use qubit_atomic::Atomic;

fn increment_even_only(atomic: &Atomic<i32>) -> Result<i32, &'static str> {
    let mut current = atomic.load();
    loop {
        // Only increment even values
        if current % 2 != 0 {
            return Err("Value is odd");
        }

        let new = current + 2;
        match atomic.compare_set(current, new) {
            Ok(_) => return Ok(new),
            Err(actual) => current = actual, // Retry
        }
    }
}

fn main() {
    let atomic = Atomic::<i32>::new(10);
    match increment_even_only(&atomic) {
        Ok(new_value) => println!("Successfully incremented to: {}", new_value),
        Err(e) => println!("Failed: {}", e),
    }
    assert_eq!(atomic.load(), 12);
}

Functional Updates

use qubit_atomic::Atomic;

fn main() {
    let atomic = Atomic::<i32>::new(10);

    // Update using a function (returns old value)
    let old_value = atomic.fetch_update(|x| {
        if x < 100 {
            x * 2
        } else {
            x
        }
    });

    assert_eq!(old_value, 10);
    assert_eq!(atomic.load(), 20);
    println!("Updated value: {}", atomic.load());

    // Update and return the committed new value
    let new_value = atomic.update_and_get(|x| x + 5);
    assert_eq!(new_value, 25);
    assert_eq!(atomic.load(), 25);

    // Conditional update and return the committed new value
    let accepted_new = atomic.try_update_and_get(|x| (x < 100).then_some(x + 5));
    assert_eq!(accepted_new, Some(30));
    assert_eq!(atomic.load(), 30);

    // Accumulate operation (returns old value)
    let old_result = atomic.fetch_accumulate(5, |a, b| a + b);
    assert_eq!(old_result, 30);
    assert_eq!(atomic.load(), 35);

    // Accumulate and return the committed new value
    let accumulated = atomic.accumulate_and_get(5, |a, b| a + b);
    assert_eq!(accumulated, 40);
    assert_eq!(atomic.load(), 40);
    println!("Accumulated value: {}", atomic.load());
}

Atomic Reference

use qubit_atomic::AtomicRef;
use std::sync::Arc;

#[derive(Debug, Clone)]
struct Config {
    timeout: u64,
    max_retries: u32,
}

fn main() {
    let config = Arc::new(Config {
        timeout: 1000,
        max_retries: 3,
    });

    let atomic_config = AtomicRef::new(config);

    // Update configuration
    let new_config = Arc::new(Config {
        timeout: 2000,
        max_retries: 5,
    });

    let old_config = atomic_config.swap(new_config);
    println!("Old config: {:?}", old_config);
    println!("New config: {:?}", atomic_config.load());

    // Update using a function (returns old value)
    let old = atomic_config.fetch_update(|current| {
        Arc::new(Config {
            timeout: current.timeout * 2,
            max_retries: current.max_retries + 1,
        })
    });

    println!("Previous config: {:?}", old);
    println!("Updated config: {:?}", atomic_config.load());

    // Update and return the committed new reference
    let updated = atomic_config.update_and_get(|current| {
        Arc::new(Config {
            timeout: current.timeout + 500,
            max_retries: current.max_retries + 1,
        })
    });
    println!("Committed config: {:?}", updated);

    // Short-lived read without cloning the Arc on the fast path
    let snapshot = atomic_config.load_guard();
    println!("Snapshot config: {:?}", snapshot);

    // Conditional update; returns the committed new reference or None if rejected
    let accepted = atomic_config.try_update_and_get(|current| {
        (current.timeout < 10_000).then_some(Arc::new(Config {
            timeout: current.timeout + 1000,
            max_retries: current.max_retries,
        }))
    });
    assert!(accepted.is_some());
}

Boolean Flag

use qubit_atomic::Atomic;
use std::sync::Arc;

struct Service {
    running: Arc<Atomic<bool>>,
}

impl Service {
    fn new() -> Self {
        Self {
            running: Arc::new(Atomic::<bool>::new(false)),
        }
    }

    fn start(&self) {
        // Only start if not already running
        if self.running.set_if_false(true).is_ok() {
            println!("Service started successfully");
        } else {
            println!("Service is already running");
        }
    }

    fn stop(&self) {
        // Only stop if currently running
        if self.running.set_if_true(false).is_ok() {
            println!("Service stopped successfully");
        } else {
            println!("Service is already stopped");
        }
    }

    fn is_running(&self) -> bool {
        self.running.load()
    }
}

fn main() {
    let service = Service::new();

    service.start();
    assert!(service.is_running());

    service.start(); // Duplicate start will fail

    service.stop();
    assert!(!service.is_running());

    service.stop(); // Duplicate stop will fail
}

Floating-Point Atomics

use qubit_atomic::Atomic;
use std::sync::Arc;
use std::thread;

fn main() {
    let sum = Arc::new(Atomic::<f32>::new(0.0));
    let mut handles = vec![];

    // Spawn 10 threads, each adds 100 times
    for _ in 0..10 {
        let sum = sum.clone();
        let handle = thread::spawn(move || {
            for _ in 0..100 {
                sum.fetch_add(0.01);
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    // Note: Due to floating-point precision, result may not be exactly 10.0
    let result = sum.load();
    println!("Sum: {:.6}", result);
    println!("Error: {:.6}", (result - 10.0).abs());
}

API Reference

Common Operations

MethodDescriptionMemory Ordering
new(value)Create new atomic-
load()Load current valueAcquire
store(value)Store new valueRelease
swap(value)Swap value, return oldAcqRel
compare_set(current, new)CAS operation, return explicit success/failureAcqRel/Acquire
compare_and_exchange(current, new)CAS operation, return observed valueAcqRel/Acquire
fetch_update(f)Functional update, return oldAcqRel/Acquire
update_and_get(f)Functional update, return newAcqRel/Acquire
try_update(f)Conditional functional update, return Option<old>AcqRel/Acquire
try_update_and_get(f)Conditional functional update, return Option<new>AcqRel/Acquire
inner()Access underlying backend type-

Primitive Weak CAS Operations

These methods are available on primitive Atomic<T> specializations. They are not available on AtomicRef<T>, which exposes strong pointer CAS only.

MethodDescriptionMemory Ordering
compare_set_weak(current, new)Weak CAS, return explicit success/failureAcqRel/Acquire
compare_and_exchange_weak(current, new)Weak CAS, return Ok(observed) or Err(actual)AcqRel/Acquire

Integer Operations

MethodDescriptionMemory Ordering
fetch_inc()Post-increment, return oldRelaxed
fetch_dec()Post-decrement, return oldRelaxed
fetch_add(delta)Post-add, return oldRelaxed
fetch_sub(delta)Post-subtract, return oldRelaxed
fetch_inc_with_ordering(ordering)Post-increment, return oldCaller-provided
fetch_dec_with_ordering(ordering)Post-decrement, return oldCaller-provided
fetch_add_with_ordering(delta, ordering)Post-add, return oldCaller-provided
fetch_sub_with_ordering(delta, ordering)Post-subtract, return oldCaller-provided
fetch_mul(factor)Post-multiply, return oldAcqRel (CAS loop)
fetch_div(divisor)Post-divide, return oldAcqRel (CAS loop)
fetch_and(value)Bitwise AND, return oldAcqRel
fetch_or(value)Bitwise OR, return oldAcqRel
fetch_xor(value)Bitwise XOR, return oldAcqRel
fetch_not()Bitwise NOT, return oldAcqRel
fetch_max(value)Atomic max, return oldAcqRel
fetch_min(value)Atomic min, return oldAcqRel
fetch_update(f)Functional update, return oldAcqRel/Acquire
update_and_get(f)Functional update, return newAcqRel/Acquire
try_update(f)Conditional functional update, return Option<old>AcqRel/Acquire
try_update_and_get(f)Conditional functional update, return Option<new>AcqRel/Acquire
fetch_accumulate(x, f)Accumulate, return oldAcqRel/Acquire
accumulate_and_get(x, f)Accumulate, return newAcqRel/Acquire

Primitive integer operations intentionally use wrapping arithmetic on overflow and underflow, matching Rust atomic integer semantics. Use AtomicCount or AtomicSignedCount when overflow or underflow should be rejected. The _with_ordering variants are available only for integer read-modify-write counter arithmetic, for cases where the counter value is also used as a synchronization signal.

AtomicCount / AtomicSignedCount operations

MethodAtomicCountAtomicSignedCountMemory OrderingDescription
new(value)usizeisize-Create a count
zero()YesYes-Create a zero value
get()usizeisizeAcquireRead the current value
is_zero()YesYesAcquireCheck whether the value is zero
is_positive()YesYesAcquireCheck whether the value is positive
is_negative()NoYesAcquireCheck whether the value is negative
inc()YesYesAcqRel/AcquireIncrement by one, return new value
dec()Panic on underflowAllows negative valuesAcqRel/AcquireDecrement by one, return new value
add(delta)Panic on overflowPanic on overflow/underflowAcqRel/AcquireAdd delta, return new value
sub(delta)Panic on underflowPanic on overflow/underflowAcqRel/AcquireSubtract delta, return new value
try_add(delta)None on overflowNone on overflow/underflowAcqRel/AcquireChecked add
try_dec()None at zeroNoAcqRel/Acquire (AtomicCount only)Checked decrement
try_sub(delta)None on underflowNone on overflow/underflowAcqRel/AcquireChecked subtract

Shared-owner wrapper operations

The ArcAtomic* wrappers dereference to their underlying atomic container, so you can call operations such as load, fetch_inc, store, inc, and sub directly on the wrapper.

MethodAvailable OnDescription
new(value)ArcAtomic<T>, ArcAtomicCount, ArcAtomicSignedCountCreate a new shared wrapper from an initial value
new(Arc<T>)ArcAtomicRef<T>Create a shared atomic reference from an existing Arc<T>
from_value(value)ArcAtomicRef<T>Create a shared atomic reference from an owned value
from_atomic(...)ArcAtomic<T>Wrap an existing Atomic<T>
from_atomic_ref(...)ArcAtomicRef<T>Wrap an existing AtomicRef<T>
from_count(...)ArcAtomicCount, ArcAtomicSignedCountWrap an existing count container
from_arc(arc)All ArcAtomic* wrappersWrap an existing Arc<...> container
as_arc()All ArcAtomic* wrappersBorrow the underlying Arc<...>
into_arc()All ArcAtomic* wrappersConsume the wrapper and return the underlying Arc<...>
strong_count()All ArcAtomic* wrappersReturn the number of strong Arc owners

Boolean Operations

MethodDescriptionMemory Ordering
fetch_set()Set to true, return oldAcqRel
fetch_clear()Set to false, return oldAcqRel
fetch_not()Negate, return oldAcqRel
fetch_and(value)Logical AND, return oldAcqRel
fetch_or(value)Logical OR, return oldAcqRel
fetch_xor(value)Logical XOR, return oldAcqRel
set_if_false(new)CAS if falseAcqRel/Acquire
set_if_true(new)CAS if trueAcqRel/Acquire

Floating-Point Operations

MethodDescriptionMemory Ordering
fetch_add(delta)Atomic add, return oldAcqRel (CAS loop)
fetch_sub(delta)Atomic subtract, return oldAcqRel (CAS loop)
fetch_mul(factor)Atomic multiply, return oldAcqRel (CAS loop)
fetch_div(divisor)Atomic divide, return oldAcqRel (CAS loop)
fetch_update(f)Functional update, return oldAcqRel/Acquire
update_and_get(f)Functional update, return newAcqRel/Acquire
try_update(f)Conditional functional update, return Option<old>AcqRel/Acquire
try_update_and_get(f)Conditional functional update, return Option<new>AcqRel/Acquire

Floating-point CAS operations (compare_set, compare_and_exchange, and weak variants) compare raw to_bits() representations, not PartialEq. Values such as 0.0 and -0.0 compare equal but do not CAS-match, and NaN payload bits must match exactly. Use compare_set when you need an explicit success result, or compare to_bits() values yourself.

Memory Ordering Strategy

Operation TypeDefault OrderingReason
Pure Read (load())AcquireEnsure reading latest value
Pure Write (store())ReleaseEnsure write visibility
Read-Modify-Write (swap(), CAS)AcqRelEnsure both read and write correctness
Atomic<T> counter arithmetic (fetch_inc(), fetch_dec(), fetch_add(), fetch_sub())RelaxedPure metrics; no need to sync other data
Ordered integer counter arithmetic (fetch_*_with_ordering)Caller-providedState-signal counters that need explicit synchronization
CAS-based arithmetic and updates (fetch_mul(), fetch_div(), fetch_update(), update_and_get(), try_update(), try_update_and_get(), fetch_accumulate(), accumulate_and_get())AcqRel / AcquireCAS loop standard semantics
AtomicCount / AtomicSignedCount (inc(), dec())CAS loopValues used as concurrent state signals
Bitwise Operations (fetch_and(), fetch_or())AcqRelUsually used for flag synchronization
Max/Min Operations (fetch_max(), fetch_min())AcqRelOften used with threshold checks

Advanced Usage: Explicit Ordering and Direct Access

For integer counter arithmetic that needs synchronization semantics, use the ordered variants before reaching for inner():

use std::sync::atomic::Ordering;
use qubit_atomic::Atomic;

let atomic = Atomic::<i32>::new(0);

// 99% of scenarios: use simple API
let value = atomic.load();

// State-signal counter: keep the wrapper while choosing ordering explicitly
atomic.fetch_add_with_ordering(1, Ordering::AcqRel);

// Lowest-level escape hatch: direct backend access
let value = atomic.inner().load(Ordering::Relaxed);
atomic.inner().store(42, Ordering::Release);

Comparison with JDK

FeatureJDKQubit AtomicNotes
Basic Types3 typesAtomic<T> specializations; atomic::primitive::* for const initializationRust supports more integer, floating-point, boolean, and counter use cases
Memory OrderingImplicit (volatile)Defaults + ordered integer RMW helpers + inner() optionalRust more flexible
Weak CASweakCompareAndSetcompare_set_weak on primitive Atomic<T>Equivalent
Reference TypeAtomicReference<V>AtomicRef<T>Rust uses Arc<T>
AtomicCount / AtomicSignedCountManual compositionAtomicCount, AtomicSignedCountNon-negative / signed counts for state tracking
Shared OwnershipUsually object referencesArcAtomic<T>, ArcAtomicRef<T>, ArcAtomicCount, ArcAtomicSignedCountConvenience wrappers for shared atomic containers
NullabilityAllows nullUse Option<Arc<T>>Rust no null pointers
Bitwise OperationsPartial supportFull supportRust more powerful
Max/Min OperationsJava 9+ supportSupportedEquivalent
API Count~20 methods/type~29 methods/typeRust provides more convenience methods

Performance Considerations

Zero-Cost Abstraction

Primitive wrappers use #[repr(transparent)] and #[inline] so the generic API compiles down to the backend atomic operations:

use qubit_atomic::Atomic;
use std::sync::atomic::Ordering;

// Our wrapper
let atomic = Atomic::<i32>::new(0);
let value = atomic.load();

// Compiles to the same code as
let atomic = std::sync::atomic::AtomicI32::new(0);
let value = atomic.load(Ordering::Acquire);

When to Use inner()

99% of scenarios: Use default API, which already provides optimal performance.

1% of scenarios: Use inner() only when:

Golden Rule: Default API first, inner() as last resort.

Testing & Code Coverage

This project maintains comprehensive test coverage with detailed validation of all functionality.

Running Tests

# Run all tests
cargo test

# Run benchmarks
cargo bench --bench atomic_bench

# List benchmark scenarios
cargo bench --bench atomic_bench -- --list

# Run with coverage report
./coverage.sh

# Generate text format report
./coverage.sh text

# Run CI checks (format, clippy, test, coverage)
./ci-check.sh

Coverage Metrics

See COVERAGE.md for detailed coverage statistics.

Dependencies

Runtime dependencies are intentionally small:

License

Copyright (c) 2025 - 2026. Haixing Hu, Qubit Co. Ltd. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

See LICENSE for the full license text.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Guidelines

Author

Haixing Hu - Qubit Co. Ltd.

More Rust libraries from Qubit are published under the qubit-ltd organization on GitHub.

---

Repository: https://github.com/qubit-ltd/rs-atomic