docs / Getting Started / Memory Model

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.