Memory Model
Miri's memory model is built on a single promise: you never write memory annotations. The compiler infers ownership, manages reference counts, and proves linear flows so it can elide the bookkeeping. The only memory-related concept that ever appears in your source code is the out keyword (covered below).
Under the hood, every value falls into one of three buckets:
- Auto-copy types — Primitives (
int,float,bool) and small all-primitive structs (≤ 128 bytes) are copied bitwise on assignment. Zero overhead, no reference counting at all. - Managed types — Strings, collections (
List,Map,Array,Set), and any struct or class with managed fields are tracked with reference counts. The compiler emits IncRef/DecRef instructions automatically — even for elements deep inside nested collections. - Resource types — Any type that defines a
fn drop(self)method. These are single-owner: aliasing is forbidden, and the compiler tracks them strictly so you cannot use one after passing it away.
Copy-on-Write: assignment shares, mutation forks
When you assign a managed value to a new variable, both bindings point at the same underlying buffer — there is no eager deep copy. The buffer's reference count goes up by one. The instant either side mutates the value, Miri silently forks: it copies the buffer, decrements the old RC, sets the new RC to 1, and applies the mutation to the fresh copy.
use system.io
use system.collections.list
fn main()
let a = List([1, 2, 3])
var b = a // No copy yet — both share the same buffer
b.push(4) // Mutation triggers Copy-on-Write
println(f"{a.length()} {b.length()}") // 3 4
// RC is incremented on share, decremented when each goes out of scope.
// The buffer is freed automatically when the last owner releases it.
The result is the best of both worlds: value semantics (mutating b never changes a), but with the performance of reference passing when no one mutates. CoW applies to List, Map, Array, Set, and String.
Zero-cost RC elision
The Perceus optimization pass analyses every function for linear flows. When the compiler proves that a value is created, used once, and discarded — never aliased, never escaping — it removes all of the IncRef/DecRef calls for that value entirely. You write idiomatic code. The compiler emits straight-line allocation and drop with no atomic counter traffic on the hot path.
Automatic recursive cleanup
When a managed value's RC reaches zero, Miri runs a compiler-generated destructor that, in order: (1) calls the user's fn drop(self) if one is defined, (2) recursively decrements every managed field, (3) frees the allocation. You never write .close(), .dispose(), or try/finally.