Error Handling

MIND provides comprehensive error diagnostics with detailed messages, source locations, and actionable suggestions.

Error Codes

Every diagnostic carries a stable code naming the Core v1 pipeline phase that produced it. The leading digit isthe phase — there is one code space, and it is partitioned by compiler stage, not by error kind:

PhaseCodeDescription
ParseE1xxxSyntax errors
Type-checkE2xxxType mismatches, inference failures, shape validation
IR verificationE3xxxFailures of the public IR invariants
AutodiffE4xxxNon-differentiable or ambiguous gradient requests
MLIR loweringE5xxxFailures translating canonical IR into MLIR

Shape validation for Core v1 operators is surfaced during type checking, inside the E2xxx space:

  • E2101 — broadcast compatibility
  • E2102 — rank or shape expectation mismatches (including invalid reductions)
  • E2103 — matmul inner-dimension mismatches

The MAP protocol server returns short transport-layer codes (E005, E101E104) in its own response space. Those are protocol responses, not compiler diagnostics, and never overlap the four-digit pipeline codes above. See the MAP protocol page.

Diagnostic formats

mindc emits human, single-line, and machine-readable diagnostics:

mindc --diagnostic-format human   # default; multi-line with spans and notes
mindc --diagnostic-format short   # single line, grep-friendly
mindc --diagnostic-format json    # one diagnostic per line of JSON

Human output uses consistent phase prefixes (error[parse], error[type-check], …), adds caret highlights when a span is available, and respects --color / MINDC_COLOR. Every error variant propagates a non-zero exit code.

Example Diagnostics

Parse error:

error[parse][E1001]: unexpected `)`, expected an expression
  --> parseerr.mind:2:13
   |     let x = );
   |             ^

Type error — MIND has no implicit int↔float conversion (RFC 0011):

error[type-check][E2015]: no implicit int↔float conversion (RFC 0011); write the value in the annotated type (e.g. `5.0`) or use an explicit `as` cast
  --> typemismatch.mind:2:21
   |     let loss: i64 = 1.5;
   |                     ^^^

Implicit narrowing is rejected rather than silently truncating:

error[type-check][E2004]: implicit narrowing Scalar[i64] -> Scalar[i32] loses data for `small`; use an explicit `as Scalar[i32]` cast
  --> narrow.mind:3:22
   |     let small: i32 = big;
   |                      ^^^

Unknown identifiers carry a suggestion:

error[type-check][E2002]: unknown identifier `nope` — did you mean `None`?
  --> undef.mind:2:12
   |     return nope;
   |            ^^^^

Machine-readable diagnostics

--diagnostic-format json emits one line-delimited JSON object per diagnostic, with a stable shape:

{"phase":"type-check","code":"E2015","severity":"error","message":"no implicit int↔float conversion (RFC 0011); write the value in the annotated type (e.g. `5.0`) or use an explicit `as` cast","span":{"file":"typemismatch.mind","line":2,"column":21,"length":3},"notes":[],"help":null}

Runtime faults

The E1xxxE5xxx codes above are compile-timediagnostics. At runtime, MIND’s rule is that a fault is deterministicrather than undefined: an out-of-bounds array access is a deterministic bounds trap, never a clamp and never a silent read of adjacent memory. A compile-provable out-of-bounds access may be rejected at compile time instead.

The observable form of that trap is currently an ABI detail, not settled language semantics: the native backend exits with a fixed status, and the reference evaluator raises a hard error naming the index and the length. A typed panic / Resultsurface for runtime faults is not yet shipped, so no runtime error-code space is defined. Arithmetic that other languages leave undefined is definedinstead of trapped — see the determinism contract for x / 0, INT_MIN / -1, overflow, and oversized shifts.

Result Type

For recoverable errors, use the Result type:

fn load_model(path: &[u8]) -> Result<Model, Error> {
    if !exists(path) {
        return Err(Error::NotFound(path));
    }
    // ...
    Ok(model)
}

fn main() {
    match load_model("model.mind.bin") {
        Ok(model) => run(model),
        Err(e) => print("Failed to load: ", e),
    }
}

Sum types and pattern matching (Result/Ok/Err with match) are part of the executable subset today, and the type checker fails loudly on nonexistent enum variants, undeclared assignment targets, return-type and condition scalar-class mismatches, and calls to non-function values. Slice-typed parameters such as &[u8]are shown for illustration — slices are not yet in the executable subset.

Learn More

See the full error catalog at mind-spec/errors.md.