cargo / atomic / audit
cargo : atomic @ 0.6.1
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

concurrency-documentedconcurrency-impl-correctconcurrency-impl-documentedconcurrency-impl-safeconcurrency-impl-testedconcurrency-safehas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

Audit of atomic 0.6.1, a no_std generic Atomic<T> wrapper dispatching to native core::sync::atomic types or a spinlock-array fallback. The unsafe code is sound and minimal, gated by bytemuck::NoUninit. Three low-severity quality findings; benign and safe to use.

Report

Subject

atomic is a small no_std Rust library by Amanieu d'Antras providing a generic Atomic<T> wrapper backed by core::sync::atomic operations for any T: bytemuck::NoUninit. When the size and alignment of T match a native atomic type (AtomicU{8,16,32,64}, optionally U128 on nightly), operations dispatch to the native primitive via mem::transmute_copy; otherwise an in-crate spinlock array provides a lock-based fallback. Optional features add serde (de)serialization and nightly support for 128-bit atomics. The crate has a single runtime dependency, bytemuck.

Methodology

The published crate was compared against the upstream Git tag at the recorded commit using diff -r; only standard cargo manifest normalisation and the addition of .cargo_vcs_info.json / Cargo.lock account for the differences. All four source files (src/lib.rs, src/ops.rs, src/fallback.rs, src/serde_impl.rs; ~1.4k LOC) were read in full. The dispatch macros, the spinlock primitives, the unsafe boundary, and the trait bounds on the public API were reviewed for soundness. Cargo.toml was inspected for feature gating and dependency surface.

Results

The crate contents match upstream byte-for-byte modulo manifest normalisation; no binary artefacts are present (justifying has-binaries) and no build script or proc-macro is present (justifying has-build-exec and has-install-exec).

The library is no_std and #![no_std], with extern crate std behind #[cfg(any(test, feature = "std"))]. It performs no filesystem, network, environment, cryptographic, JIT, interpreter, or process operations (justifying uses-filesystem, uses-network, uses-environment, uses-crypto, uses-jit, uses-interpreter, uses-exec, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm). It uses and implements concurrency primitives (justifying uses-concurrency, impl-concurrency).

unsafe code is used throughout (justifying uses-unsafe): inside the dispatch helpers in src/ops.rs, the spinlock primitives in src/fallback.rs, and the unsafe impl Sync / get_mut / into_inner methods in src/lib.rs. Each unsafe block was reviewed:

  • unsafe impl<T: Copy + Send> Sync for Atomic<T> is justified by the atomicity guarantee of the underlying operations.
  • The dispatch helpers cast *mut T to *mut AtomicU{N} only after a size+alignment match, making the reinterpretation sound; T: NoUninit ensures the bit pattern is fully initialised.
  • mem::transmute_copy is used to convert between T and the chosen AtomicU{N}'s underlying integer; size equality is enforced by the matching arm.
  • The spinlock fallback uses ptr::read / ptr::write under a held spinlock; T: NoUninit again ensures the bit copy is well-defined. The compare_exchange fallback compares via bytemuck::bytes_of, matching the bit-pattern comparison semantics of the native compare_exchange.

The unsafe is therefore safe (justifying unsafe-safe) and minimal (justifying unsafe-minimal). However, none of the internal unsafe fn declarations carry a # Safety doc paragraph and none of the call sites carry a // SAFETY: comment; FINDING-1 captures this (justifying unsafe-documented as false).

The spinlock implementation (src/fallback.rs:21-99) uses 64 cache-line-aligned AtomicUsize locks with a compiler-rt-style address hash, releases via an RAII LockGuard (correct under unwind), and performs only non-panicking operations between lock and unlock (except cmp::min/cmp::max, which can panic only if the user's Ord impl panics — the RefUnwindSafe comment in src/lib.rs:75-80 acknowledges this). The concurrency implementation is correct and documented (justifying concurrency-impl-correct, concurrency-impl-documented, concurrency-impl-safe, concurrency-safe, concurrency-documented). It is not exercised under loom or ThreadSanitizer, so concurrency-impl-tested was not asserted.

Three low-severity quality findings were recorded: FINDING-1 on missing safety documentation, FINDING-2 on the asymmetric Deserialize bound (missing NoUninit), and FINDING-3 on a stray doc-comment fragment in compare_exchange_weak.

The crate has an inline unit-test suite (#[cfg(test)] mod tests, 14 #[test] functions exercising every integer width and three custom #[repr(C)] types; justifying has-unit-tests). There are no integration tests, fuzz tests, or property-based tests (justifying has-integration-tests, has-fuzz-tests, has-property-tests). The tests exercise correctness on a single thread but do not run under loom, ThreadSanitizer, or miri, and do not include adversarial scheduling or stress-testing of the spinlock fallback (justifying unsafe-tested = false and concurrency-impl-tested = false).

No malicious patterns, hidden network calls, or suspicious cfg-gated payloads were observed (justifying is-benign).

Conclusion

atomic 0.6.1 is a small, well-engineered concurrency primitive from a reputable Rust author. The unsafe code is necessary, correctly gated by T: NoUninit, and sound; the spinlock fallback is a clean and conventional design. The only findings are low-severity quality issues. The crate is benign and safe to use.

Findings(3)

FINDING-1 quality low

Internal unsafe fns lack `# Safety` docs and call-site safety comments

All of the dispatch helpers in src/ops.rs (atomic_load, atomic_store, atomic_swap, atomic_compare_exchange, atomic_compare_exchange_weak, atomic_add, atomic_sub, atomic_and, atomic_or, atomic_xor, atomic_min, atomic_max, atomic_umin, atomic_umax, map_result) and the spinlock-backed primitives in src/fallback.rs (the eleven pub unsafe fns) are declared unsafe but carry no # Safety doc paragraph describing the invariants callers must uphold (e.g. "dst must be a valid, aligned pointer to a T: NoUninit for the entire operation; no other thread may concurrently access *dst through a non-atomic reference").

The call sites in src/lib.rs (e.g. unsafe { ops::atomic_load(self.inner_ptr(), order) } at src/lib.rs:153, and the macro-generated fetch_add / fetch_sub / etc. callers) likewise have no // SAFETY: line. The invariants do hold in this crate (inner_ptr returns a pointer into an UnsafeCell<MaybeUninit<T>> of the correct type, kept alive by &self, and T: NoUninit is enforced on the public API), so the unsafe is sound, but the lack of documentation makes auditing the helpers in isolation harder and makes future refactors more risk-prone. This justifies unsafe-safe (the unsafe is correct) while unsafe-documented is false.

FINDING-2 quality low

Deserialize impl for `Atomic<T>` does not require `T: NoUninit`

In src/serde_impl.rs:21-30, Deserialize is implemented for Atomic<T> with only the bound T: for<'a> Deserialize<'a>, while Serialize (lines 8-19) correctly requires T: NoUninit + Serialize.

Atomic::new is callable for any T (it's a pub const fn not gated on NoUninit), so deserialisation succeeds in producing an Atomic<T> value, but the resulting value is unusable: every operation on Atomic<T> other than new / get_mut / into_inner requires T: NoUninit in the impl block (src/lib.rs:119). This is harmless (no soundness impact) but asymmetric and surprising: a user could deserialize an Atomic<MyType> and later be confused that no atomic operations work. Adding T: NoUninit to the Deserialize bound would surface the constraint at the deserialization site.

FINDING-3 quality low

Minor doc typo: duplicated phrase in `compare_exchange_weak` rustdoc

The doc comment of Atomic::compare_exchange_weak (src/lib.rs:203-216) ends with "...must be equivalent or weaker than the success ordering. / success ordering." — the phrase "success ordering." is repeated on a new line, presumably an editing artefact. The published rustdoc shows the stray trailing fragment.

Annotations(4)

src/fallback.rs

src/fallback.rs, line 21-99

#[repr(align(64))]
struct SpinLock(AtomicUsize);

impl SpinLock {
    fn lock(&self) {
        while self
            .0
            .compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
            .is_err()
        {
            while self.0.load(Ordering::Relaxed) != 0 {
                hint::spin_loop();
            }
        }
    }

    fn unlock(&self) {
        self.0.store(0, Ordering::Release);
    }
}

// A big array of spinlocks which we use to guard atomic accesses. A spinlock is
// chosen based on a hash of the address of the atomic object, which helps to
// reduce contention compared to a single global lock.
macro_rules! array {
    (@accum (0, $($_es:expr),*) -> ($($body:tt)*))
        => {array!(@as_expr [$($body)*])};
    (@accum (1, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)*))};
    (@accum (2, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)* $($es,)*))};
    (@accum (4, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (2, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (8, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (4, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (16, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (8, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (32, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (16, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (64, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (32, $($es,)* $($es),*) -> ($($body)*))};

    (@as_expr $e:expr) => {$e};

    [$e:expr; $n:tt] => { array!(@accum ($n, $e) -> ()) };
}
static SPINLOCKS: [SpinLock; 64] = array![SpinLock(AtomicUsize::new(0)); 64];

// Spinlock pointer hashing function from compiler-rt
#[inline]
fn lock_for_addr(addr: usize) -> &'static SpinLock {
    // Disregard the lowest 4 bits.  We want all values that may be part of the
    // same memory operation to hash to the same value and therefore use the same
    // lock.
    let mut hash = addr >> 4;
    // Use the next bits as the basis for the hash
    let low = hash & (SPINLOCKS.len() - 1);
    // Now use the high(er) set of bits to perturb the hash, so that we don't
    // get collisions from atomic fields in a single object
    hash >>= 16;
    hash ^= low;
    // Return a pointer to the lock to use
    &SPINLOCKS[hash & (SPINLOCKS.len() - 1)]
}

#[inline]
fn lock(addr: usize) -> LockGuard {
    let lock = lock_for_addr(addr);
    lock.lock();
    LockGuard(lock)
}

struct LockGuard(&'static SpinLock);
impl Drop for LockGuard {
    #[inline]
    fn drop(&mut self) {
        self.0.unlock();
    }
}

Spinlock-based fallback for atomicity emulation: 64 cache-line-aligned AtomicUsize locks, hash-addressed (compiler-rt style) to reduce contention. hint::spin_loop() on contention; no fairness/backoff but adequate for short critical sections. Lock release via RAII LockGuard; correct on panic-unwind paths because critical sections perform only ptr::read/ptr::write and (for min/max) cmp::min/cmp::max after the write. Justifies concurrency-impl-correct.

src/fallback.rs, line 102-198

pub unsafe fn atomic_load<T>(dst: *mut T) -> T {
    let _l = lock(dst as usize);
    ptr::read(dst)
}

#[inline]
pub unsafe fn atomic_store<T>(dst: *mut T, val: T) {
    let _l = lock(dst as usize);
    ptr::write(dst, val);
}

#[inline]
pub unsafe fn atomic_swap<T>(dst: *mut T, val: T) -> T {
    let _l = lock(dst as usize);
    ptr::replace(dst, val)
}

#[inline]
pub unsafe fn atomic_compare_exchange<T: NoUninit>(
    dst: *mut T,
    current: T,
    new: T,
) -> Result<T, T> {
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    // compare_exchange compares with memcmp instead of Eq
    let a = bytemuck::bytes_of(&result);
    let b = bytemuck::bytes_of(&current);
    if a == b {
        ptr::write(dst, new);
        Ok(result)
    } else {
        Err(result)
    }
}

#[inline]
pub unsafe fn atomic_add<T: Copy>(dst: *mut T, val: T) -> T
where
    Wrapping<T>: ops::Add<Output = Wrapping<T>>,
{
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    ptr::write(dst, (Wrapping(result) + Wrapping(val)).0);
    result
}

#[inline]
pub unsafe fn atomic_sub<T: Copy>(dst: *mut T, val: T) -> T
where
    Wrapping<T>: ops::Sub<Output = Wrapping<T>>,
{
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    ptr::write(dst, (Wrapping(result) - Wrapping(val)).0);
    result
}

#[inline]
pub unsafe fn atomic_and<T: Copy + ops::BitAnd<Output = T>>(dst: *mut T, val: T) -> T {
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    ptr::write(dst, result & val);
    result
}

#[inline]
pub unsafe fn atomic_or<T: Copy + ops::BitOr<Output = T>>(dst: *mut T, val: T) -> T {
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    ptr::write(dst, result | val);
    result
}

#[inline]
pub unsafe fn atomic_xor<T: Copy + ops::BitXor<Output = T>>(dst: *mut T, val: T) -> T {
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    ptr::write(dst, result ^ val);
    result
}

#[inline]
pub unsafe fn atomic_min<T: Copy + cmp::Ord>(dst: *mut T, val: T) -> T {
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    ptr::write(dst, cmp::min(result, val));
    result
}

#[inline]
pub unsafe fn atomic_max<T: Copy + cmp::Ord>(dst: *mut T, val: T) -> T {
    let _l = lock(dst as usize);
    let result = ptr::read(dst);
    ptr::write(dst, cmp::max(result, val));
    result
}

Spinlock-backed primitives use ptr::read/ptr::write; no # Safety doc on any of the pub unsafe fns. See FINDING-1. compare_exchange correctly uses bytemuck::bytes_of for byte-wise comparison (matching native compare_exchange semantics, which compare bit patterns rather than Eq).

src/lib.rs

src/lib.rs, line 67-82

pub struct Atomic<T> {
    // The MaybeUninit is here to work around rust-lang/rust#87341.
    v: UnsafeCell<MaybeUninit<T>>,
}

// Atomic<T> is only Sync if T is Send
unsafe impl<T: Copy + Send> Sync for Atomic<T> {}

// Given that atomicity is guaranteed, Atomic<T> is RefUnwindSafe if T is
//
// This is trivially correct for native lock-free atomic types. For those whose
// atomicity is emulated using a spinlock, it is still correct because the
// `Atomic` API does not allow doing any panic-inducing operation after writing
// to the target object.
#[cfg(feature = "std")]
impl<T: RefUnwindSafe> RefUnwindSafe for Atomic<T> {}

Generic atomic wrapper backed by UnsafeCell<MaybeUninit<T>> for transparent layout; unsafe impl Sync is gated on T: Copy + Send so the Atomic<T> API can hand out values by value. The MaybeUninit works around rust-lang/rust#87341 (niche-optimisation interaction). Justifies uses-unsafe, uses-concurrency, impl-concurrency, concurrency-impl-safe, concurrency-impl-documented.

src/lib.rs, line 404-829

mod tests {
    use super::{Atomic, Ordering::*};
    use bytemuck::NoUninit;
    use core::mem;

    #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, NoUninit)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    #[repr(C)]
    struct Foo(u8, u8);

    #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, NoUninit)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    #[repr(C)]
    struct Bar(u64, u64);

    #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, NoUninit)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    #[repr(C)]
    struct Quux(u32);

    #[cfg(feature = "serde")]
    fn assert_serde<T>(atomic: &Atomic<T>, value: T)
    where
        T: NoUninit
            + PartialEq
            + std::fmt::Debug
            + for<'a> serde::Deserialize<'a>
            + serde::Serialize,
    {
        let s = serde_json::to_string(atomic).unwrap();
        assert_eq!(s, serde_json::to_string(&value).unwrap());

        let x: Atomic<T> = serde_json::from_str(&s).unwrap();
        assert_eq!(x.load(SeqCst), value);
    }

    #[test]
    fn atomic_bool() {
        let a = Atomic::new(false);
        assert_eq!(
            Atomic::<bool>::is_lock_free(),
            cfg!(target_has_atomic = "8"),
        );
        assert_eq!(format!("{:?}", a), "Atomic(false)");
        assert_eq!(a.load(SeqCst), false);
        a.store(true, SeqCst);
        assert_eq!(a.swap(false, SeqCst), true);
        assert_eq!(a.compare_exchange(true, false, SeqCst, SeqCst), Err(false));
        assert_eq!(a.compare_exchange(false, true, SeqCst, SeqCst), Ok(false));
        assert_eq!(a.fetch_and(false, SeqCst), true);
        assert_eq!(a.fetch_or(true, SeqCst), false);
        assert_eq!(a.fetch_xor(false, SeqCst), true);
        assert_eq!(a.load(SeqCst), true);

        #[cfg(feature = "serde")]
        assert_serde(&a, true);
    }

    #[test]
    fn atomic_i8() {
        let a = Atomic::new(0i8);
        assert_eq!(Atomic::<i8>::is_lock_free(), cfg!(target_has_atomic = "8"));
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        // Make sure overflows are handled correctly
        assert_eq!(a.fetch_sub(-56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), -74);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(-25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_i16() {
        let a = Atomic::new(0i16);
        assert_eq!(
            Atomic::<i16>::is_lock_free(),
            cfg!(target_has_atomic = "16")
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(-56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 182);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(-25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_i32() {
        let a = Atomic::new(0i32);
        assert_eq!(
            Atomic::<i32>::is_lock_free(),
            cfg!(target_has_atomic = "32")
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(-56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 182);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(-25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_i64() {
        let a = Atomic::new(0i64);
        assert_eq!(
            Atomic::<i64>::is_lock_free(),
            cfg!(target_has_atomic = "64") && mem::align_of::<i64>() == 8
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(-56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 182);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(-25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_i128() {
        let a = Atomic::new(0i128);
        assert_eq!(
            Atomic::<i128>::is_lock_free(),
            cfg!(feature = "nightly") & cfg!(target_has_atomic = "128")
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(-56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 182);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(-25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_isize() {
        let a = Atomic::new(0isize);
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(-56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 182);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(-25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_u8() {
        let a = Atomic::new(0u8);
        assert_eq!(Atomic::<u8>::is_lock_free(), cfg!(target_has_atomic = "8"));
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 70);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_u16() {
        let a = Atomic::new(0u16);
        assert_eq!(
            Atomic::<u16>::is_lock_free(),
            cfg!(target_has_atomic = "16")
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 70);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_u32() {
        let a = Atomic::new(0u32);
        assert_eq!(
            Atomic::<u32>::is_lock_free(),
            cfg!(target_has_atomic = "32")
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 70);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_u64() {
        let a = Atomic::new(0u64);
        assert_eq!(
            Atomic::<u64>::is_lock_free(),
            cfg!(target_has_atomic = "64") && mem::align_of::<u64>() == 8
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 70);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_u128() {
        let a = Atomic::new(0u128);
        assert_eq!(
            Atomic::<u128>::is_lock_free(),
            cfg!(feature = "nightly") & cfg!(target_has_atomic = "128")
        );
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 70);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_usize() {
        let a = Atomic::new(0usize);
        assert_eq!(format!("{:?}", a), "Atomic(0)");
        assert_eq!(a.load(SeqCst), 0);
        a.store(1, SeqCst);
        assert_eq!(a.swap(2, SeqCst), 1);
        assert_eq!(a.compare_exchange(5, 45, SeqCst, SeqCst), Err(2));
        assert_eq!(a.compare_exchange(2, 3, SeqCst, SeqCst), Ok(2));
        assert_eq!(a.fetch_add(123, SeqCst), 3);
        assert_eq!(a.fetch_sub(56, SeqCst), 126);
        assert_eq!(a.fetch_and(7, SeqCst), 70);
        assert_eq!(a.fetch_or(64, SeqCst), 6);
        assert_eq!(a.fetch_xor(1, SeqCst), 70);
        assert_eq!(a.fetch_min(30, SeqCst), 71);
        assert_eq!(a.fetch_max(25, SeqCst), 30);
        assert_eq!(a.load(SeqCst), 30);

        #[cfg(feature = "serde")]
        assert_serde(&a, 30);
    }

    #[test]
    fn atomic_foo() {
        let a = Atomic::default();
        assert_eq!(Atomic::<Foo>::is_lock_free(), false);
        assert_eq!(format!("{:?}", a), "Atomic(Foo(0, 0))");
        assert_eq!(a.load(SeqCst), Foo(0, 0));
        a.store(Foo(1, 1), SeqCst);
        assert_eq!(a.swap(Foo(2, 2), SeqCst), Foo(1, 1));
        assert_eq!(
            a.compare_exchange(Foo(5, 5), Foo(45, 45), SeqCst, SeqCst),
            Err(Foo(2, 2))
        );
        assert_eq!(
            a.compare_exchange(Foo(2, 2), Foo(3, 3), SeqCst, SeqCst),
            Ok(Foo(2, 2))
        );
        assert_eq!(a.load(SeqCst), Foo(3, 3));

        #[cfg(feature = "serde")]
        assert_serde(&a, Foo(3, 3));
    }

    #[test]
    fn atomic_bar() {
        let a = Atomic::default();
        assert_eq!(Atomic::<Bar>::is_lock_free(), false);
        assert_eq!(format!("{:?}", a), "Atomic(Bar(0, 0))");
        assert_eq!(a.load(SeqCst), Bar(0, 0));
        a.store(Bar(1, 1), SeqCst);
        assert_eq!(a.swap(Bar(2, 2), SeqCst), Bar(1, 1));
        assert_eq!(
            a.compare_exchange(Bar(5, 5), Bar(45, 45), SeqCst, SeqCst),
            Err(Bar(2, 2))
        );
        assert_eq!(
            a.compare_exchange(Bar(2, 2), Bar(3, 3), SeqCst, SeqCst),
            Ok(Bar(2, 2))
        );
        assert_eq!(a.load(SeqCst), Bar(3, 3));

        #[cfg(feature = "serde")]
        assert_serde(&a, Bar(3, 3));
    }

    #[test]
    fn atomic_quxx() {
        let a = Atomic::default();
        assert_eq!(
            Atomic::<Quux>::is_lock_free(),
            cfg!(target_has_atomic = "32")
        );
        assert_eq!(format!("{:?}", a), "Atomic(Quux(0))");
        assert_eq!(a.load(SeqCst), Quux(0));
        a.store(Quux(1), SeqCst);
        assert_eq!(a.swap(Quux(2), SeqCst), Quux(1));
        assert_eq!(
            a.compare_exchange(Quux(5), Quux(45), SeqCst, SeqCst),
            Err(Quux(2))
        );
        assert_eq!(
            a.compare_exchange(Quux(2), Quux(3), SeqCst, SeqCst),
            Ok(Quux(2))
        );
        assert_eq!(a.load(SeqCst), Quux(3));

        #[cfg(feature = "serde")]
        assert_serde(&a, Quux(3));
    }
}

Unit-test module (#[cfg(test)] mod tests, 14 #[test] functions) exercises load/store/swap/compare_exchange/fetch_*/min/max for each integer width and for three custom #[repr(C)] types (size-2 Foo, size-16 Bar, size-4 Quux), plus serde round-trips when the feature is enabled. Justifies has-unit-tests. No fuzz or property tests; no loom/miri/TSan tests visible in the published crate.

src/ops.rs

src/ops.rs, line 18-98

macro_rules! match_atomic {
    ($type:ident, $atomic:ident, $impl:expr, $fallback_impl:expr) => {
        match mem::size_of::<$type>() {
            #[cfg(target_has_atomic = "8")]
            1 if mem::align_of::<$type>() >= 1 => {
                type $atomic = core::sync::atomic::AtomicU8;

                $impl
            }
            #[cfg(target_has_atomic = "16")]
            2 if mem::align_of::<$type>() >= 2 => {
                type $atomic = core::sync::atomic::AtomicU16;

                $impl
            }
            #[cfg(target_has_atomic = "32")]
            4 if mem::align_of::<$type>() >= 4 => {
                type $atomic = core::sync::atomic::AtomicU32;

                $impl
            }
            #[cfg(target_has_atomic = "64")]
            8 if mem::align_of::<$type>() >= 8 => {
                type $atomic = core::sync::atomic::AtomicU64;

                $impl
            }
            #[cfg(all(feature = "nightly", target_has_atomic = "128"))]
            16 if mem::align_of::<$type>() >= 16 => {
                type $atomic = core::sync::atomic::AtomicU128;

                $impl
            }
            #[cfg(feature = "fallback")]
            _ => $fallback_impl,
            #[cfg(not(feature = "fallback"))]
            _ => panic!("Atomic operations for type `{}` are not available as the `fallback` feature of the `atomic` crate is disabled.", core::any::type_name::<$type>()),
        }
    };
}

macro_rules! match_signed_atomic {
    ($type:ident, $atomic:ident, $impl:expr, $fallback_impl:expr) => {
        match mem::size_of::<$type>() {
            #[cfg(target_has_atomic = "8")]
            1 if mem::align_of::<$type>() >= 1 => {
                type $atomic = core::sync::atomic::AtomicI8;

                $impl
            }
            #[cfg(target_has_atomic = "16")]
            2 if mem::align_of::<$type>() >= 2 => {
                type $atomic = core::sync::atomic::AtomicI16;

                $impl
            }
            #[cfg(target_has_atomic = "32")]
            4 if mem::align_of::<$type>() >= 4 => {
                type $atomic = core::sync::atomic::AtomicI32;

                $impl
            }
            #[cfg(target_has_atomic = "64")]
            8 if mem::align_of::<$type>() >= 8 => {
                type $atomic = core::sync::atomic::AtomicI64;

                $impl
            }
            #[cfg(all(feature = "nightly", target_has_atomic = "128"))]
            16 if mem::align_of::<$type>() >= 16 => {
                type $atomic = core::sync::atomic::AtomicI128;

                $impl
            }
            #[cfg(feature = "fallback")]
            _ => $fallback_impl,
            #[cfg(not(feature = "fallback"))]
            _ => panic!("Atomic operations for type `{}` are not available as the `fallback` feature of the `atomic` crate is disabled.", core::any::type_name::<$type>()),
        }
    };
}

match_atomic! / match_signed_atomic! dispatch on size_of::<T>() and align_of::<T>() to choose a same-size native AtomicU8/U16/U32/U64 (and 128 on nightly) and reinterpret the storage via transmute_copy. The size+alignment check guarantees the cast is sound; types that don't match any native atomic fall through to the spinlock fallback. Without the fallback feature, unmatched sizes panic with a clear message — a documented compile-time-ish failure, not UB.

src/serde_impl.rs

Serde integration: Serialize loads with Ordering::Relaxed and forwards. Deserialize builds an Atomic from a deserialised T via Self::new, but its bound is missing NoUninit (see FINDING-2).