atomic-bitflags 0.2.0

Atomic storage for bitflags flag sets
Documentation
  • Coverage
  • 58.82%
    20 out of 34 items documented2 out of 21 items with examples
  • Size
  • Source code size: 44.9 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 241.6 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 2s Average build duration of successful builds.
  • all releases: 2s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • sacha-renault/atomic-bitflags
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • sacha-renault

atomic-bitflags

Atomic storage for bitflags flag sets.

AtomicFlags<F> stores the bits of a bitflags type in a standard library atomic integer. Reads and writes are lock-free, and every operation takes an Ordering exactly like AtomicU32 or AtomicU8.

Install

[dependencies]
atomic-bitflags = "0.1"
bitflags = "2"

Use case

Runtime configuration that several threads read and a few threads update: feature switches, log levels, or service modes. It is also a replacement for a mutex around a flag set when you only need atomic reads, writes, and set/clear/toggle operations.

use atomic_bitflags::AtomicFlags;
use bitflags::bitflags;
use std::sync::Arc;
use std::sync::atomic::Ordering;

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct Features: u8 {
        const CACHE = 1 << 0;
        const TRACE = 1 << 1;
        const STRICT = 1 << 2;
    }
}

fn main() {
    let features = Arc::new(AtomicFlags::new(Features::CACHE));

    // Enable tracing from another thread.
    let remote = Arc::clone(&features);
    std::thread::spawn(move || {
        remote.set(Features::TRACE, Ordering::Release);
    })
    .join()
    .unwrap();

    let current = features.load(Ordering::Acquire);
    assert!(current.contains(Features::CACHE | Features::TRACE));

    // `take` claims flags atomically: only one caller can claim STRICT.
    features.set(Features::STRICT, Ordering::SeqCst);
    assert_eq!(features.take(Features::STRICT, Ordering::SeqCst), Features::STRICT);
    assert!(features.take(Features::STRICT, Ordering::SeqCst).is_empty());
}

Operations on a shared instance take &self, so wrap the value in Arc to share it. AtomicFlags is Send + Sync.

API

Method Operation Returns
new(flags) initialize the new AtomicFlags
from_storage(atomic) const initialize from a pre-built atomic the new AtomicFlags
load(ordering) atomic load current flags
store(flags, ordering) atomic store nothing
swap(flags, ordering) atomic swap previous flags
compare_exchange(current, new, success, failure) compare and swap Ok(previous) or Err(actual)
compare_exchange_weak(current, new, success, failure) same, may fail spuriously Ok(previous) or Err(actual)
set(flags, ordering) atomic OR previous flags
unset(flags, ordering) atomic AND NOT previous flags
clear(ordering) store empty nothing
take(flags, ordering) atomic AND NOT requested flags that were set
peek(flags, ordering) atomic load requested flags that are set
fetch_and(flags, ordering) atomic AND previous flags
fetch_or(flags, ordering) atomic OR previous flags
fetch_xor(flags, ordering) atomic XOR previous flags
toggle(flags, ordering) alias for fetch_xor previous flags
fetch_nand(flags, ordering) atomic NAND previous flags
try_update(set_order, fetch_order, f) apply f until it succeeds Ok(previous) or Err(current)
into_inner() consume final flags

set and unset are convenience wrappers around fetch_or and fetch_and. The bitwise fetch_* methods return the full previous value; try_update returns Ok(previous) on success, or Err(current) if the closure returns None.

compare_exchange and compare_exchange_weak follow the standard atomics: on failure they return the actual current value, and the failure ordering may not be stronger than the success ordering.

take and peek return only the requested flags. take claims them with a single atomic operation; peek is a plain load, so another thread may change the value immediately after.

fetch_nand keeps the standard atomic semantics: the new value is !(current & flags), so bits outside flags are set as well.

Static initialization

from_storage is const, so a flag set can live in a static:

use std::sync::atomic::AtomicU8;

static FEATURES: AtomicFlags<Features> =
    AtomicFlags::from_storage(AtomicU8::new(Features::CACHE.bits()));

The atomic type must match the bits type of the flag set (u8 uses AtomicU8, u64 uses AtomicU64, and so on).

Limits

  • u128 and i128 are not supported because Rust has no 128-bit atomic integer.
  • 64-bit flag sets need a target with 64-bit atomics, like any AtomicU64.
  • Unknown bits are preserved on load (from_bits_retain), not truncated.
  • No ordering is chosen for you; pick one with the same rules as the standard library atomics.

License

MIT OR Apache-2.0.