Error Diagnostics
Every error in mino produces a structured diagnostic: a plain map with stable keys, a classified error code, source location, and a human-readable message. The same model serves the REPL, embedded hosts, and user code.
What You See
When an error occurs in the REPL or a script, mino renders a diagnostic with the error code, message, source file, line, column, and a snippet of the source with a caret pointer:
error[MTY001]: count: expected a collection, got int
--> app.clj:18:3
|
18 | (count 42)
| ^Reader errors show the exact position:
error[MRE001]: unterminated string literal
--> app.clj:7:8
|
7 | (def y "unterminated
| ^Errors Are Data
In catch handlers, the exception value is always a map with these keys:
| Key | Type | Meaning |
|---|---|---|
:mino/kind | keyword | Category: :reader, :syntax, :eval/type, :eval/arity, :eval/bounds, :user, etc. |
:mino/code | string | Stable error code, e.g. "MTY001" |
:mino/phase | keyword | Processing phase: :read, :eval |
:mino/message | string | Human-readable primary message |
:mino/data | any | The original thrown value (for user exceptions) |
Example: catching a type error and inspecting it.
(try
(count 42)
(catch e
(println (:mino/kind e)) ;; :eval/type
(println (:mino/code e)) ;; "MTY001"
(println (:mino/message e)) ;; "count: expected a collection, got int"
))throw and catch
(throw x) accepts any value. The catch handler always receives a diagnostic map, regardless of what was thrown. The original value is accessible via (ex-data e):
(try
(throw "oops")
(catch e
(println (ex-data e)) ;; "oops"
(println (ex-message e)) ;; "oops"
(println (error? e)) ;; true
))If you throw an ex-info map, ex-data and ex-message extract the payload transparently:
(try
(throw (ex-info "not found" {:code 404}))
(catch e
(println (ex-data e)) ;; {:code 404}
(println (ex-message e)) ;; "not found"
))Helper Functions
| Function | Description |
|---|---|
(error? x) | True if x is a map with :mino/kind |
(ex-data e) | Extract the data payload from a diagnostic map or ex-info |
(ex-message e) | Extract the message from a diagnostic map or ex-info |
(last-error) | Return the last diagnostic map, or nil |
(ex-info msg data) | Create an exception map for throwing |
Error Code Catalog
Every error has a stable code that can be matched programmatically. Codes are grouped by prefix:
| Prefix | Category | Examples |
|---|---|---|
MRE | Reader | Unterminated string, unexpected delimiter, unterminated collection |
MSY | Syntax | Malformed def, fn, let special forms |
MNS | Name resolution | Unresolved symbol, missing namespace alias |
MAR | Arity | Wrong number of arguments |
MTY | Type mismatch | Operation received unsupported value type |
MBD | Bounds | Index out of range |
MCT | Contract | Precondition or invariant violation |
MHO | Host | Capability denied, host callback failure |
MIO | I/O | slurp, spit, sh, file-system access failures |
MOM | Out of memory | Allocator exhausted during a user-visible computation (e.g. bignum arithmetic) |
MOV | Overflow | Integer overflow on + / - / * / inc / dec when the bignum-promoting variant (+' etc.) was not used |
MLM | Limit | Step limit, heap limit, recursion depth exceeded |
MUS | User | User-thrown exception |
MIN | Internal | Runtime invariant violation or unhandled internal-state error |
C API
Embedders have structured access to the last error:
/* Backward-compatible string access */
const char *mino_last_error(S);
/* Structured diagnostic (internal fields) */
const mino_diag *mino_last_diag(S);
/* Diagnostic as a mino map with :mino/* keys */
mino_val *mino_last_error_map(S);
/* Render to buffer in compact or pretty mode */
int mino_render_diag(S, diag, MINO_DIAG_RENDER_PRETTY, buf, sizeof(buf));Existing code using mino_last_error() continues to work unchanged. The string is rendered from the structured diagnostic internally.