miri-lang · diff
git:20260909.1e01938 to git:20260909.8d2903e
1 added, 1 removed. Audit A to A.
---
name: miri-lang
description: Writing Miri source files — syntax, type system, and iteration workflow
---
# Miri Language Essentials
This skill teaches the core syntax and idioms for writing `.mi` source files. It covers what the language supports, what it explicitly does not support, and the workflow for arriving at correct code.
## Positive Grammar
Miri's syntax is indentation-sensitive. Variables use space-separated type annotations (not colon form). Here is the core:
```miri
use system.collections.list
struct Point
x int
y int
enum Color
Red
Blue
fn add(a int, b int) int
a + b
fn demo_color(color Color)
match color
Color.Red: println("red")
Color.Blue: println("blue")
class Shape
fn area() int: 0
trait Drawable
fn draw()
class Circle implements Drawable
fn draw(): println("circle")
fn identity<T>(x T) T
x
fn main()
let x int = 5
var y int = 10
y = 20
let p = Point(3, 4)
let sum = add(x, y)
let items = List([1, 2, 3])
println(f"{sum} {p.x} {items.length()} {identity(7)}")
demo_color(Color.Red)
match Result.Ok(5)
Result.Ok(v): println(f"ok {v}")
Result.Err: println("err")
let maybe int? = None
match maybe
Some(v): println(f"some {v}")
None: println("none")
```
This core powers all Miri programs. The rest of this skill covers what the language **does not** support, because those gaps matter just as much as the syntax that works.
## Anti-Hallucination: Syntax That Does Not Exist
Miri deliberately leaves these off the surface to avoid ambiguity. Every example below is a compile error. Before guessing a member name, ask: `miri view --type String --public` lists what can be called on a type, including what it inherits and what a trait supplies, and needs no path for a prelude type.
### Type Annotations on Bindings
The colon form does not exist. Use space-separated type annotations instead:
```miri,fails=MER_PAR_001,expects-message=Expected an expression, but found :
let x: int = 5
```
Use space-separated type annotations:
```miri
let x int = 5
let y int = 10
```
If the type cannot be inferred, annotate with a cast:
```miri
let z = 5 as i64 // explicit cast when needed
```
### Literal Type Suffixes
No suffix syntax like `1i32` or `3.14f64`:
```miri,fails=MER_TYP_034,expects-message=Undefined variable: i32
let x = 42i32
```
Miri infers numeric width from context. Annotate with an explicit cast when needed:
```miri
let x = 42
let y = 42 as i64
```
### Result and Option Match Arms: The Asymmetry
**Result** requires the constructor prefix (`Result.Ok`, `Result.Err`). Omitting it causes a type error:
```miri,fails=MER_TYP_038,expects-message=Expected enum variant pattern like Result.Ok
fn main()
let r = Result.Ok(5)
match r
Ok(x): x
Err: 0
```
Correct form uses the prefix:
```miri
fn main()
let r = Result.Ok(5)
match r
Result.Ok(x): x
Result.Err: 0
```
**Nullability (`T?`)** uses bare `Some` and `None` without a prefix:
```miri
fn main()
let x int? = 5
match x
Some(v): v
None: 0
```
The key difference: `Result` is a standalone type that requires its constructor name in patterns; `T?` is syntactic sugar for nullability where `Some`/`None` are bare constructors.
### Match Arm Bodies: One Line or an Indented Block
An arm header may end with a colon or not, and its body may sit on that line or
indented below it. All four spellings mean the same thing. An indented body may
hold several statements; a same-line body is one expression. Alternative
patterns are separated by `|`, and an arm may carry an `if` guard:
```miri
fn weigh(n int) int
var weight = 0
match n
m if m > 10:
weight = weight + 10
weight = weight + 5
0 | 1: weight = weight + 1
_
weight = weight + 2
weight
fn main()
println(f"{weigh(50)} {weigh(0)} {weigh(5)}")
```
### Collection Constructor Forms
The three forms are distinct and mean different things:
- `List([1, 2, 3])` — allocate and populate from a literal
- `List<int>()` — allocate empty with explicit type argument
- `{}` — an empty `Map<void, void>` (not a Set or List)
Confusing them is the most common error:
```miri,fails=MER_TYP_036,expects-message=Cannot instantiate generic class 'List<T>' without explicit type arguments
use system.collections.list
fn main()
let l = List() // Missing generic type argument
```
Correct forms:
```miri
use system.collections.list
use system.collections.map
use system.collections.set
fn main()
let l = List([1, 2, 3])
let empty = List<int>()
let m = Map<String, int>()
let s = Set<int>()
```
### No `let mut` — Use `var` Instead
The keyword `let mut` does not exist:
```miri,fails=MER_PAR_001,expects-message=Expected an end of statement, but found identifier
let mut x = 5
```
Use `var`:
```miri
var x = 5
x = 10
```
### Parameters Are Immutable
Function parameters are immutable, even if the argument is mutable. To mutate the caller's variable, the method must exist in the stdlib or be written as a free function accepting a mutable reference pattern (not yet available). The closest common case:
```miri,fails=MER_TYP_042,expects-message=Cannot assign to element of immutable variable
use system.collections.list
fn increment(arr [int]):
arr[0] = arr[0] + 1
fn main():
var a = List([5, 10])
increment(a)
```
Methods that mutate use `.set()`:
```miri
use system.collections.list
fn increment(arr [int]):
arr.set(0, arr[0] + 1)
fn main():
var a = List([5, 10])
increment(a)
```
## Module Resolution
Bare imports are searched in this order: stdlib (via `MIRI_STDLIB_PATH` env var), the entry file's directory, then the current working directory. Local imports (`use local.*`) resolve only against the entry file's directory. The stdlib cannot be shadowed; `system` is reserved. Set `MIRI_STDLIB_PATH` to override the stdlib location when the binary is not beside `stdlib/`; `miri view --stdlib-root` prints the roots actually searched and whether each exists. `miri view` accepts a module name wherever it accepts a path, so `miri view system.io --outline --public` lists what a module offers without knowing where it lives on disk.
## Iteration and Control Flow
**For loops over collections and ranges:**
```miri
use system.collections.list
fn main()
let xs = List([1, 2, 3])
for x in xs
println(f"{x}")
for n in 0..3
println(f"n {n}")
var i = 0
while i < 3
i = i + 1
```
**Early return and if/else chains:**
```miri
fn classify(n int) String
if n < 0
return "negative"
else if n == 0
return "zero"
"positive"
fn main()
println(classify(-1))
println(classify(0))
println(classify(5))
```
## String Conversion and Collections
**f-strings are the only int→String conversion path:**
```miri
fn main()
let n = 42
let s = f"{n}"
println("count: " + s)
println(f"n={n} half={n / 2}")
```
Attempting other conversion methods fails:
```miri,fails=MER_TYP_002,expects-message=cannot add String and int
fn main()
let n = 42
println("count: " + n)
```
```miri,fails=MER_TYP_033,expects-message=Type 'int' does not have members
fn main()
let n = 42
println(n.to_string())
```
**Collections — length, push, first(), is_empty, contains:**
```miri
use system.collections.list
fn main()
var l = List<int>()
l.push(10)
l.push(20)
println(f"len {l.length()}")
println(f"empty {l.is_empty()}")
println(f"has {l.contains(10)}")
match l.first()
Some(v): println(f"first {v}")
None: println("none")
```
**Map — index-set, get, iteration:**
```miri
use system.collections.map
fn main()
var m = Map<String, int>()
m["a"] = 1
m["b"] = 2
println(f"size {m.length()}")
match m.get("a")
Some(v): println(f"a {v}")
None: println("missing")
for k in m
println(f"key {k}")
```
Map iteration happens over keys. A common mistake is calling `.keys()`:
```miri,fails=MER_TYP_033,expects-message=has no field or method 'keys'
use system.collections.map
fn main()
var m = Map<String, int>()
for k in m.keys()
println(k)
```
**String methods:**
```miri
fn main()
let s = " Hello, World "
let t = s.trim()
println(t.to_lower())
println(f"{t.length()}")
let has = t.contains("World")
println(f"{has}")
for part in t.split(", ")
println(part)
match "42".to_int()
Some(n): println(f"parsed {n}")
None: println("not a number")
```
**Exit codes from main:**
```miri
fn main() int
let ok = false
if ok
return 0
return 3
```
## Verification Loop
The workflow for writing correct Miri code:
1. **miri check** — compile and collect diagnostics: `miri check myfile.mi --format json`
2. **miri run** — execute and see real output: `miri run myfile.mi`
3. **miri test** — run tests in a directory: `miri test --dir <DIR>`
- 4. **miri view** — read scoped code: `miri view myfile.mi --outline`, `--fn name`, `--type Name`, or a module name in place of the path
+ 4. **miri view** — read scoped code: `miri view myfile.mi --outline`, `--fn name`, `--type Name`, or a module name in place of the path. Add `--raw` for the file's own bytes (comments and all) behind their line numbers, alone or with `--fn`; without it the output is canonical, not literal
5. **miri patch** — make scoped edits: `miri patch myfile.mi --replace-in-fn name --old text --new text`
6. **miri fix** — repair errors: `miri explain CODE`, `miri fix --plan myfile.mi`, `miri fix --apply --yes myfile.mi`
7. **miri agent** — tool integration (see `tools/agent_client.py` and `docs/agent-protocol.md`)
**Loop:** check → (if errors) explain + fix --plan → fix --apply → run/test → re-check. Iterate until clean.
The auto-applicable repairs are:
- `add-import`: Import a name that resolves in exactly one module.
- `arrow-return-type`: Drop the `->` before a return type.
- `colon-annotation`: Drop the `:` before a type annotation.
- `concat-to-formatted-string`: Rewrite a `+` chain joining text to values as one f-string.
- `drop-extra-arguments`: Drop positional arguments a call does not declare.
- `drop-iterator-accessor`: Drop a `keys` accessor on a keyed collection and iterate it directly.
- `let-mut-to-var`: Rewrite a `let mut` binding as `var`.
- `let-to-var`: Rebind an immutable declaration as mutable.
- `null-to-none`: Rewrite `null`, `nil` or `nullptr` as `None`.
- `println-bang`: Drop the `!` from a macro-style call.
- `qualify-variant-pattern`: Prefix a bare variant pattern with the enum that declares it.