iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
MIR is rustc’s Mid-level Intermediate Representation: a compiler view of Rust code organized as basic blocks, explicit control flow, and typed storage locations. To read it, follow each block’s statements and terminator, then track which places are read or written and what values their rvalues produce. This makes MIR useful for understanding borrow checking as well as later compiler work, but MIR is an implementation representation—not a stable contract for Rust source code.
Where MIR fits in rustc
The Rust Compiler Development Guide describes MIR as an intermediate representation built from HIR and deliberately simpler than Rust’s source syntax. MIR follows parsing and successive lowering and checking stages, including lowering through THIR. From there, it is used in borrow checking and also supports optimization and code generation.
It is more useful to think of these stages as a network of compiler queries and dependencies than as one rigid, one-way conveyor belt. For a rough orientation, HIR is earlier and closer to source structure, MIR makes control flow and typed operations explicit, and LLVM IR is a later representation involved in code generation. That contrast is about their position and broad purpose, not a full account of their semantics.
Free tools Windows power users keep installed
One-click scans. No signup required.
Start with the control-flow graph
MIR is organized as a control-flow graph (CFG): nodes represent basic blocks, and edges represent possible transfers between blocks. A basic block is a sequence of statements followed by a terminator. Statements perform actions with a single successor; the terminator ends the block and determines where execution may continue. A branch or return therefore appears as an explicit control-flow decision rather than being buried in a nested source expression.
#1 Best Overall
When reading a function, first identify its blocks and ask what each block can lead to. Follow the outgoing edges from each terminator, including separate paths through branches. This gives you the shape of the function before you try to interpret every assignment.
Track locations separately from values
Locals are indexed storage locations
MIR locals are numbered storage locations, commonly written like _1. The return value is held in _0. These are compiler-local identifiers, not names you necessarily wrote in the Rust source. Use the function’s local declarations and types to orient yourself when they are shown in a dump.
Rank #2
Places say where
A place identifies a location that can be read or written. It may refer to a whole local, such as _1, or a projection within one, such as _1.f for a field. Thinking “where is this value?” helps distinguish a location from the value currently stored there.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Rvalues say what is produced
An rvalue is an expression that produces a value and commonly appears on the right side of a MIR assignment. In an assignment, read the left side as the destination place and the right side as the value-producing operation. MIR’s terms are compiler IR vocabulary; its assignments are not simply ordinary Rust expressions copied into a different font.
Rank #3
A practical method for reading a function
- Find the blocks. Note each basic block and the terminator that closes it. Treat terminators as the map of where execution can go next.
- Trace one path at a time. Follow a block’s successor edges and record which branch or transfer leads to each next block.
- Read each statement as a location change or action. Identify the destination place, then determine what the rvalue on the right produces or what other action the statement performs.
- Track locals and projections. Keep the distinction between a whole local and a projected place such as a field; note where values are read, written, or transferred.
- Return to the terminator. Once the block’s statements make sense, use its terminator to continue the trace or identify the function’s exit.
For every block, the useful questions are: where can control go next, what statement changes a local or place, what value does an rvalue produce, and what choice does the terminator make? Applying those questions path by path is more reliable than trying to translate a MIR dump back into one source-level expression.
Why MIR helps explain borrow checking
The compiler guide says borrow checking operates on MIR and checks properties such as whether a variable is initialized before use, whether a value is moved more than once, whether a value is moved while borrowed, and whether a place is accessed or mutated in ways that conflict with active borrows. MIR’s simpler structure makes these checks flow-sensitive: the compiler can reason about what is true at particular points and along particular control-flow paths.
This is also the basis for non-lexical lifetimes (NLL). Rather than treating a borrow’s region as simply the textual scope of a variable, MIR-based checking derives regions from control-flow locations, allowing the compiler to reason about where a borrow is actually relevant.
Recommended Free Tools
The guide’s high-level borrow-checking sequence
The Rust Compiler Development Guide presents the following broad implementation outline. It is a mental model of the process, not a promise that every release uses an exhaustive, immutable sequence of steps.
- Prepare a local copy of MIR and replace regions with inference variables.
- Run dataflow analyses to compute what is moved and when.
- Type-check the MIR and collect region constraints.
- Infer region values over control-flow locations.
- Determine which borrows are in scope.
- Walk MIR again to report violations.
What dataflow contributes
Dataflow analysis propagates facts through a graph. At a high level, an analysis applies rules to the state at a point, carries the resulting state along outgoing edges, and repeats until the facts stabilize. In compiler terminology, the rules are often described by a transfer function and the stabilized result by a fixpoint; a lattice is the mathematical structure used to combine possible facts. You do not need those terms to begin following MIR, but they help explain how information from one block affects another.
The guide gives concrete examples of rustc’s dataflow uses: finding uninitialized variables, determining which variables are live across generator yield statements, and computing which places are borrowed at a point in the control-flow graph. In each case, the graph matters because the compiler must account for values and facts along possible paths, not just inspect a line of source code in isolation.
How to inspect MIR
rustc’s MIR debugging documentation describes -Z dump-mir for writing textual MIR and -Z dump-mir-dataflow for producing a .dot graph of dataflow state at control-flow points. These are compiler debugging flags, not stable user-facing interfaces. Their availability and invocation requirements can depend on the toolchain and channel, so check the current MIR debugging guide for the exact command and supported setup.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsA textual dump is a good starting point for following blocks, assignments, and terminators. A dataflow graph can help when the specific question is how an analysis state changes across control flow. Keep in mind that a dump exposes compiler representation details that can shift between compiler releases.
What MIR can—and cannot—tell you
MIR is the useful middle view when you want to see how rustc has made storage, values, and control flow explicit between higher-level Rust structure and later code generation. It can clarify why a borrow-checking decision depends on a particular path or point in a function. It is not a stable language specification, nor is a MIR dump by itself a complete account of every compiler optimization or machine-code decision.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

