Using Miri to Catch Undefined Behavior in Unsafe Rust
Miri interprets your tests and reports undefined behavior in unsafe code: out of bounds access, use after free, bad aliasing and uninitialized reads. Here is how to run and read it.

You wrote an unsafe block, the tests pass and you are unsure. Passing tests mean little here, because undefined behavior often looks like working code. Miri runs the same tests in an interpreter that tracks every pointer and every byte, and stops at the first rule violation. It is the cheapest way to turn "I think this is sound" into "this particular run found nothing wrong". It is the checking step in unsafe Rust explained.
Rust nightly, Miri bundled with the component.
Install and run
rustup +nightly component add miri
cargo +nightly miri testThe first run builds a special sysroot, which takes a minute. After that, each test is interpreted.
A bug it finds
pub fn dangling() -> i32 {
let v = vec![1, 2, 3];
let p = v.as_ptr();
drop(v);
unsafe { *p }
}
#[test]
fn reads_freed_memory() {
dangling();
}Native cargo test usually passes, because the freed memory still holds the old bytes. Miri stops:
error: Undefined Behavior: memory access failed: alloc1234 has been freed, so this pointer is dangling
--> src/lib.rs:5:14
|
5 | unsafe { *p }
| ^^ memory access failed: alloc1234 has been freed, so this pointer is danglingThe report shows where the allocation was made and where it was freed, which usually answers the question without a debugger.
Aliasing violations
let mut x = 1;
let p = &mut x as *mut i32;
let r = &mut x;
unsafe { *p = 2 };
*r = 3;Miri's Stacked Borrows model reports that the pointer p was invalidated when r was created from x, so the write through p is not allowed. This class of bug is nearly invisible without a tool, since no hardware check exists for it. Whether a given pattern is allowed is still being refined in the language, and Tree Borrows is an alternative model: run both with MIRIFLAGS="-Zmiri-tree-borrows" when unsure. See aliasing in undefined behavior cases.
Useful flags
Set them through MIRIFLAGS:
| Flag | Purpose |
|---|---|
-Zmiri-many-seeds=0..32 | run with 32 scheduler seeds to explore thread interleavings |
-Zmiri-strict-provenance | catch int-to-pointer casts that lose provenance |
-Zmiri-disable-isolation | allow access to the real clock, environment and files |
-Zmiri-ignore-leaks | do not fail on leaked memory |
What it cannot do
- Run FFI calls into C, or inline assembly.
- Check paths your tests do not reach.
- Run quickly: a test that loops a million times will take minutes.
- Find logic errors, deadlocks (it can report them) or performance problems.
Combine it with sanitizers on a real binary for FFI heavy code, and with fuzzing to explore more inputs.
In CI
A separate job with a pinned nightly date keeps results stable. Restrict it to the unsafe modules, with cargo miri test --lib unsafe_module. The tool is documented in the Miri README.


