Rust Lesson 41 – Atomics | Dataplexa

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:

  • AtomicBool
  • AtomicI32, AtomicUsize
  • AtomicPtr

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 value
  • store() – write value
  • fetch_add() – increment value
  • fetch_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::Relaxed
  • Ordering::Acquire
  • Ordering::Release
  • Ordering::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.