po4yka/rust-skills/memory-model

Claude Code and agent skill by po4yka/rust-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when choosing or reviewing atomic orderings (Ordering::Relaxed, Acquire, Release, AcqRel, SeqCst) or declaring global state (static mut, const vs static, OnceLock, LazyLock, thread_local), including happens-before edges, fences, compare-exchange or fetch_update loops, lock-free code, and a data race seen only on ARM64. Triggers on "global variable", "lazy_static", "once_cell".

Install

npx vskill@latest install po4yka/rust-skills/memory-model

SKILL.md

Memory Model

Assume a weakly ordered target (ARM64, RISC-V, POWER). These CPUs reorder memory accesses that x86 keeps in order. An ordering bug can pass every test on x86 and fail on a phone or on an Apple silicon Mac.

Core rules

  1. Derive the ordering from the data it publishes. Name the writes that the

reader must see, then pick the weakest ordering that publishes them. Do not start from SeqCst: it hides the missing reasoning, and it does not repair an unpaired or misplaced access.

  1. Ordering is a property of a pair. A Release store synchronizes

with an Acquire load of the same atomic that reads the stored value.

  1. An atomic that publishes nothing needs only `Relaxed`. Counters and

unique ids are the usual case.

  1. Prefer a lock, `OnceLock`, or a channel over hand-rolled publication.

To publish non-atomic data through a flag you need UnsafeCell and unsafe. Treat that code as a lock-free structure: it needs a loom test and an unsafe review (the rust-unsafe skill, when it is installed).

  1. A pass on x86 proves nothing about ordering. The compiler also reorders

Relaxed accesses, so a Relaxed bug can appear on x86 after an unrelated optimization change. Use the checks in Verification.

Common mistakes

MistakeFix
Relaxed used for publish/subscribeUse Release on the store and Acquire on the load.
SeqCst used everywhere "to be safe"Derive the ordering from the data dependency. Keep SeqCst only for a named multi-atomic order.
Release on one atomic paired with Acquire on a different atomicPair the operations on the same atomic, or build an explicit synchronization chain. A SeqCst fence alone does not connect different atomics.
static mut used for shared dataUse Mutex, RwLock, OnceLock, LazyLock, or an atomic.
const used for shared state with interior mutabilityDeclare it static. A const is inlined, so every use site mutates a fresh copy.
Non-atomic data guarded only by an atomic flagGuard the payload with a lock. Hand-rolled UnsafeCell publication needs a loom test and an unsafe review.
Relaxed decrement in a reference countUse AcqRel, or Release plus an Acquire fence before the destructor.
compare_exchange on a pointer or index that can be freed and reused (ABA)Pack a generation counter with the value, or use epoch-based reclamation.
Side effect in an update or try_update closureKeep the closure free of side effects. It runs again after each lost race.
compiler_fence used to order memory between threadsUse fence or an ordered atomic. compiler_fence emits no CPU barrier.
ThreadSanitizer reports a race on fence-based codeDo not strengthen the ordering to silence it. TSan does not support fence, so the report can be false. Check with Miri or loom.
ARM64 assumed to behave like x86Run Miri and loom, and test on a weakly ordered host.

Verification

Match the check to the risk. Each check misses a class of bugs.

bash
# Miri: data races and UB on the executed paths, one schedule per seed.
MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo +nightly miri test --locked <test_filter>
# loom: all interleavings of a loom-instrumented test.
RUSTFLAGS="--cfg loom" cargo test --locked --release --test <loom_target>
# Hardware: the normal suite on an aarch64 host or device.
cargo test --locked
  • Miri runs, when a nightly toolchain with the miri component is

installed, for any change that adds or edits an atomic operation that a test reaches. Without it, report the atomic change as not checked by Miri. Each seed is one schedule, and Miri emulates only some weak memory effects, so a pass is evidence, not proof. Miri model flags and CI setup live in the rust-sanitizers-miri skill, when it is installed.

  • loom runs for a lock-free structure, a hand-written lock, or publication

through UnsafeCell. It checks only code built on its types: loom::sync atomics, loom::thread, and loom::cell::UnsafeCell for the payload. A payload in std::cell::UnsafeCell is invisible to loom, so gate the cell type with cfg(loom) too. Setup lives in the rust-test-tools skill, when it is installed. Know its two limits. It treats SeqCst loads and stores as AcqRel, so a failure in SeqCst code can be a false alarm (fence(SeqCst) is modelled). It does not explore load buffering, so a pass is not proof. A LOOM_MAX_PREEMPTIONS bound also limits what a pass covers.

  • A weakly ordered host runs the suite before you ship a lock-free

structure. An Apple silicon Mac qualifies. A finite run that does not fail proves only that this run did not fail.

Review checklist

Check each item before you approve a diff that touches atomics.

  • [ ] Every Release store has a matching Acquire load of the same atomic.
  • [ ] Every ordering is the weakest one that is still correct, with a comment

that names the data it publishes.

  • [ ] No SeqCst remains without a stated multi-atomic ordering requirement.
  • [ ] Non-atomic shared data is guarded by a lock, not by the flag alone.
  • [ ] Reference-count decrements use AcqRel, or Release plus an Acquire

fence.

  • [ ] Every compare-exchange and update failure ordering is Relaxed,

Acquire, or SeqCst, and is chosen for what the losing thread reads.

  • [ ] Every CAS on a pointer or index is safe against ABA.
  • [ ] New code uses update or try_update, not fetch_update, unless the

MSRV is below 1.95.

  • [ ] Busy-wait loops call std::hint::spin_loop() or are replaced by blocking.
  • [ ] The change has a Miri run, or the report says it is not checked by Miri

because no nightly miri component is installed. A lock-free change also has a loom test.

Global state

A global is shared state, so every rule above applies to it. Choose the declaration form first. Each form fails in a different way.

FormUse it forCost
const NAME: TA compile-time value with no identity: a limit, a table of &str.Inlined at every use site. No address. No state.
static NAME: TShared state with a const initializer: an atomic, a Mutex.One address for the process. T must be Sync.
static NAME: OnceLock<T>A value written once at start-up by its owner.One atomic load per read. get returns Option<&T>.
static NAME: LazyLock<T>A value computed on first use from a fixed initializer.One atomic load per read. Derefs to &T.
thread_local! { static NAME: T }Per-thread scratch: a buffer, a recursion depth, an RNG.No synchronization. One value per thread.

Five rules decide the choice. Each one is a compile error or a silent bug.

  1. A `static` needs `Sync`. static BAD: RefCell<u32> is rejected with

error[E0277]: RefCell<u32> cannot be shared between threads safely and the note shared static variables must have a type that implements Sync. Put the value in a Mutex or an RwLock, or use an atomic.

  1. A `const` has no single address. The compiler inlines the value at every

use site, so interior mutability in a const mutates a fresh temporary and discards it. const HITS: AtomicUsize incremented three times reads back 0. The same declaration as a static reads back 3. rustc 1.93 and later warns with const_item_interior_mutations, and clippy warns with clippy::declare_interior_mutable_const. Neither is an error, so this ships.

  1. `OnceLock` and `LazyLock` differ on panic. When the initializer panics,

OnceLock::get_or_init leaves the cell empty and the next call retries. LazyLock poisons for the life of the process: every later deref panics with LazyLock instance has previously been poisoned. Use LazyLock for an initializer that cannot fail. Use OnceLock when the value arrives from outside main, or when the first attempt can fail.

  1. A `static` is never dropped. Process exit runs no Drop on a static,

so a flush-on-drop writer in a global loses its last buffer. Flush it explicitly before main returns.

  1. `thread_local!` destructors have holes. A destructor runs when its thread

exits, but std documents that on Unix with pthread-based TLS it does not run for a value on the main thread. Never put required cleanup in thread-local state.

Do not rewrite a static mut to an atomic without reading its type. An atomic replaces a static mut that holds an integer or a bool. A static mut that holds a Vec or a String needs OnceLock or LazyLock plus a Mutex. On edition 2024 the static_mut_refs lint is deny-by-default, so every reference to the static stops the build. The cargo-workflows skill, when it is installed, has the two exact messages and the edition-2024 migration steps.

LazyLock is stable since 1.80.0 and OnceLock since 1.70.0. A workspace on a newer toolchain needs no lazy_static or static_init dependency. Keep once_cell while rust-version stays below 1.80.0, or while a global initializer returns a Result. OnceLock::get_or_try_init is still unstable on 1.98.1. It fails with error[E0658] under the feature gate once_cell_try, issue 109737.

rust,run
use std::cell::Cell;
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex, OnceLock};
// One address, computed on first deref from a fixed initializer.
static TABLE: LazyLock<Mutex<HashMap<u32, u64>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
// One address, written once at start-up by the owner of the value.
static CONFIG: OnceLock<String> = OnceLock::new();
// One value per thread. The `const` block skips the lazy-init check per access.
thread_local! {
static DEPTH: Cell<u32> = const { Cell::new(0) };
}
fn main() {
CONFIG.set(String::from("prod")).expect("set exactly once");
TABLE.lock().expect("TABLE poisoned").insert(1, 42);
DEPTH.set(DEPTH.get() + 1);
let len = TABLE.lock().expect("TABLE poisoned").len();
println!("{:?} {} {}", CONFIG.get(), len, DEPTH.get());
}

Orderings

OrderingValid onGuarantee
Relaxedload, store, read-modify-writeAtomicity and one modification order per atomic. No order for other memory.
Acquireload, read-modify-write, fence, compiler_fenceWhen it reads the value of a Release store, every write before that store is visible after it.
Releasestore, read-modify-write, fence, compiler_fencePublishes every earlier write to an Acquire load that reads this value.
AcqRelread-modify-write, fence, compiler_fenceAcquire on the read half, Release on the write half.
SeqCstallAcquire and release, plus one total order over all SeqCst operations.

An invalid ordering panics at run time: a load with Release or AcqRel, a store with Acquire or AcqRel, a fence or compiler_fence with Relaxed, and a compare-exchange or update failure ordering of Release or AcqRel. The rustc lint invalid_atomic_ordering is deny-by-default and rejects these calls at compile time when the ordering is a literal. It cannot see an ordering that arrives through a variable, so the panic remains the last line of defence.

Choosing an ordering

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
4
Repository forks
1
7-day trend score
5

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/po4yka/rust-skills/memory-model/badge)](https://verified-skill.com/skills/po4yka/rust-skills/memory-model)

Something wrong with this skill or its page? Tell us.