atomic-time 1.0.0

Thread-safe atomic time types, lock-free where supported
Documentation

Thread-safe atomic versions of Duration, SystemTime, Instant and their Option variants; lock-free on platforms with native AtomicU128 support, otherwise portable-atomic may fall back to a global lock

English | 简体中文

Introduction

atomic-time provides thread-safe atomic versions of Rust's standard time types. They are lock-free on platforms with native AtomicU128 support; otherwise, portable-atomic may fall back to a global lock. All types use AtomicU128 (via portable-atomic) under the hood and expose the same API patterns as the standard std::sync::atomic types (load, store, swap, compare_exchange, compare_exchange_weak, fetch_update).

Types

Type Wraps no_std arbitrary quickcheck / proptest
AtomicDuration Duration Yes With std With std
AtomicOptionDuration Option<Duration> Yes With std With std
AtomicSystemTime SystemTime No With std With std
AtomicOptionSystemTime Option<SystemTime> No With std With std
AtomicInstant Instant No No No
AtomicOptionInstant Option<Instant> No No No

Installation

[dependencies]
atomic-time = "1"

Feature Flags

Feature Default Description
std Yes Enables SystemTime and Instant types
serde No Enables Serialize/Deserialize for all types, including no_std builds
arbitrary No Enables arbitrary for duration and system-time types; requires std
quickcheck No Enables quickcheck for duration and system-time types, and their snapshot Clone implementations
proptest No Enables proptest strategies for duration and system-time types

For no_std environments (only AtomicDuration and AtomicOptionDuration are available):

[dependencies]
atomic-time = { version = "1", default-features = false }

The optional serde feature can also be enabled in a no_std build with features = ["serde"].

All three property-testing integrations require std. This includes arbitrary, because the current upstream arbitrary v1 crate itself uses std. Opt into one of the integrations with:

[dependencies]
atomic-time = { version = "1", features = ["arbitrary"] }
# or: atomic-time = { version = "1", features = ["quickcheck"] }
# or: atomic-time = { version = "1", features = ["proptest"] }

The quickcheck feature implements Clone for AtomicDuration, AtomicOptionDuration, AtomicSystemTime, and AtomicOptionSystemTime. Cloning performs a SeqCst load, so under concurrent writes the cloned value is the value observed at that load's atomic linearization point.

AtomicInstant and AtomicOptionInstant intentionally do not implement arbitrary, quickcheck, or proptest traits (and do not gain Clone from these features). Their encodings use a process-local baseline, so a seed or corpus cannot produce portable, deterministic values across process runs.

On some bare-metal targets without native compare-and-swap support, the final application must use Cargo feature unification to enable portable-atomic's critical-section feature and provide a target-appropriate critical-section implementation, or adopt another safe configuration from the official portable-atomic guide.

Atomic Operations

All six atomic types provide is_always_lock_free, try_update, update, fetch_min, and fetch_max; the existing fetch_update remains available for compatibility. fetch_min and fetch_max return the value observed before the operation. For Option types, ordering is None < Some; Instant comparisons are meaningful only for values using the same process-local baseline.

AtomicDuration and AtomicOptionDuration also provide fetch_saturating_add and fetch_saturating_sub, returning the old value. For AtomicDuration, these saturate at Duration::MAX and Duration::ZERO. For AtomicOptionDuration, Some values saturate while None remains None (it is not treated as zero). These names intentionally use fetch_saturating_add/sub, rather than fetch_add/sub, to avoid implying integer wrapping; saturating add/sub helpers apply only to the Duration types.

Time Semantics

AtomicSystemTime and AtomicOptionSystemTime accept only values at or after SystemTime::UNIX_EPOCH; earlier values panic.

AtomicInstant and AtomicOptionInstant encode Instant relative to a process-local baseline initialized from SystemTime::now() and Instant::now(). Within the same process, values within the platform's representable Instant range round-trip precisely. Across processes or restarts, the encoding is not portable and decoding only approximates wall-clock time. System clock adjustments can change that cross-process or restart interpretation. Behavior across sleep follows the platform's Instant semantics. Encoded instants must not be persisted as deadlines. If an extreme Duration cannot be represented by the platform's Instant, decoding falls back to the process baseline instead of panicking.

Example

use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use atomic_time::AtomicDuration;

let timeout = Arc::new(AtomicDuration::new(Duration::from_secs(30)));

// Update from another thread
let timeout_clone = timeout.clone();
std::thread::spawn(move || {
    timeout_clone.store(Duration::from_secs(60), Ordering::Release);
});
use std::sync::atomic::Ordering;
use std::time::Instant;
use atomic_time::AtomicOptionInstant;

// Track the last time an event occurred
let last_event = AtomicOptionInstant::none();
assert_eq!(last_event.load(Ordering::Relaxed), None);

last_event.store(Some(Instant::now()), Ordering::Release);
assert!(last_event.load(Ordering::Acquire).is_some());

Benchmarks

Run with cargo bench in the benchmark/ directory. Apple M4 Pro.

Duration (cargo bench --bench duration)

Implementation Single-thread load Single-thread store Contended read Load under write contention Contended store
AtomicDuration 1.04 ns 0.72 ns 1.08 ns 4.44 ns 10.7 ns
AtomicOptionDuration 1.16 ns 0.74 ns 1.20 ns 4.73 ns 14.5 ns
ArcSwap<Duration> 2.30 ns 86.5 ns 2.38 ns 15.5 ns 1,400 ns
parking_lot::RwLock 3.40 ns 2.09 ns 9.3 ns 241.8 ns 37.7 ns
std::sync::RwLock 4.44 ns 2.26 ns 411.2 ns 89.0 ns 36.8 ns

Instant (cargo bench --bench instant)

Implementation Single-thread load Single-thread store Contended read Load under write contention Contended store
AtomicInstant 2.16 ns 3.52 ns 2.17 ns 14.97 ns 22.7 ns
AtomicOptionInstant 2.36 ns 3.51 ns 2.40 ns 17.63 ns 21.2 ns
ArcSwap<Instant> 2.95 ns 74.9 ns 2.92 ns 17.53 ns 972 ns
parking_lot::RwLock 3.43 ns 2.10 ns 81.3 ns 213.9 ns 26.9 ns
std::sync::RwLock 4.48 ns 2.28 ns 422.3 ns 87.7 ns 76.9 ns

SystemTime (cargo bench --bench system_time)

Implementation Single-thread load Single-thread store Contended read Load under write contention Contended store
AtomicSystemTime 1.26 ns 3.41 ns 1.28 ns 8.89 ns 27.1 ns
AtomicOptionSystemTime 1.25 ns 3.47 ns 1.30 ns 9.17 ns 25.2 ns
ArcSwap<SystemTime> 2.29 ns 83.7 ns 2.39 ns 16.4 ns 1,227 ns
parking_lot::RwLock 3.50 ns 2.13 ns 22.0 ns 204.5 ns 31.0 ns
std::sync::RwLock 4.69 ns 2.31 ns 554.2 ns 84.4 ns 38.7 ns

Contended read = 4 background threads also reading; Load under write contention = 4 background threads writing, load measured on main thread; Contended store = 4 background threads writing, store measured on main thread. All numbers from Apple M4 Pro.

MSRV

The minimum supported Rust version is 1.85.

License

atomic-time is under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE, LICENSE-MIT for details.

Copyright (c) 2026 Al Liu.