Atomic Types in Rust
In this lesson, you will learn about atomic types in Rust, which enable lock-free concurrency for simple shared data. Atomics are faster than mutexes and are commonly used in performance-critical systems.
Rust’s atomic types are designed to be safe, efficient, and explicit.
What Are Atomic Types?
Atomic types allow multiple threads to read and modify shared data without using locks.
Operations on atomic values are guaranteed to be completed as a single, indivisible step.
Why Use Atomics?
Atomics are useful when:
- You need very fast shared state access
- The data is simple (counters, flags)
- Mutex overhead is too expensive
For complex data, mutexes are usually a better choice.
Atomic Types in Rust
Rust provides atomic types in the std::sync::atomic module.
Common atomic types include:
AtomicBoolAtomicI32,AtomicUsizeAtomicPtr
Creating an Atomic Variable
Atomic values must be wrapped in their atomic type.
use std::sync::atomic::AtomicUsize;
fn main() {
let counter = AtomicUsize::new(0);
}
Atomic Operations
Atomic types provide methods for safely updating values.
Common operations include:
load()– read valuestore()– write valuefetch_add()– increment valuefetch_sub()– decrement value
Using fetch_add()
The fetch_add() method increments the atomic value
and returns the previous value.
use std::sync::atomic::{AtomicUsize, Ordering};
fn main() {
let counter = AtomicUsize::new(0);
counter.fetch_add(1, Ordering::SeqCst);
counter.fetch_add(1, Ordering::SeqCst);
println!("{}", counter.load(Ordering::SeqCst));
}
Memory Ordering
Atomic operations require a memory ordering, which defines how operations are synchronized across threads.
Common orderings:
Ordering::RelaxedOrdering::AcquireOrdering::ReleaseOrdering::SeqCst(strongest)
For beginners, SeqCst is the safest choice.
Sharing Atomics Across Threads
Atomic values can be safely shared between threads using Arc.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let counter = Arc::new(AtomicUsize::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
counter.fetch_add(1, Ordering::SeqCst);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final count: {}", counter.load(Ordering::SeqCst));
}
Atomics vs Mutexes
Both atomics and mutexes provide safe concurrency, but they serve different purposes.
- Atomics: Fast, lock-free, limited to simple data
- Mutexes: Slower, flexible, supports complex data
Choose the right tool based on your use case.
Common Use Cases
- Counters
- Reference counts
- Status flags
- Metrics collection
📝 Practice Exercises
Exercise 1
Create an atomic integer and increment it.
Exercise 2
Use fetch_add() to update a shared counter.
Exercise 3
Share an atomic variable across multiple threads.
Exercise 4
Explain the difference between atomics and mutexes.
✅ Practice Answers
Answer 1
let a = AtomicUsize::new(0);
Answer 2
a.fetch_add(1, Ordering::SeqCst);
Answer 3
let shared = Arc::new(AtomicUsize::new(0));
Answer 4
Atomics are lock-free and faster but limited, while mutexes support complex shared data with locking.
What’s Next?
In the next lesson, you will learn about Async/Await in Rust, which allows writing asynchronous code that looks synchronous.