Skip to content

v0.3.0

Latest

Choose a tag to compare

@github-actions github-actions released this 03 Sep 11:40
· 15 commits to main since this release

Changes

Added

  • Record update. A record literal may end in ..base:
    { beats: st.beats + 1, ..st } takes the listed fields from the literal
    and every other field from st, and has st's type, so an update can
    neither add (C0010) nor remove fields. { ..base } alone is an error
    (C0060), not a copy; the base is written once, last (P0008). Lowers to
    an Erlang map update. The demos' actor states are now record aliases
    read by field and rebuilt with ..st.
  • Type aliases. type alias Name<params> = T names a type expression
    — a record, a tuple, a function type — and every use expands to it, so
    an alias has no identity: it is transparent to unification, the wire
    check, the audit format, the IR, and codegen. A tool's argument record
    and the handlers that implement it now name the shape once (the demos
    do: LogArgs and friends). A recursive alias is C0059 and opaque on
    an alias is P0007; pub type alias exports it and an importer sees the
    expansion.
  • Sequencing with ;. a; b runs a for its effects and evaluates
    to b; it is sugar for let _ = a in b, except that a must be ()
    (C0058 otherwise), so a result is never dropped by accident. Right-
    associative and looser than every operator; bodies remain single
    expressions. The demos and guides use it for effect sequencing.
  • Option is predeclared. Option<a> = Some(a) | None is seeded like
    Next, so Some(1) and a match over Some/None need no declaration;
    a user type Option<a> = … still shadows it. List stays a bare type
    name until list literals and patterns exist.
  • Irrefutable patterns in let. The binder is a pattern:
    let Config(clock, period) = config in …, let (a, b) = pair in …, and
    let _ = e in … all work, replacing the one-arm match idiom; a _
    binder emits _ = Expr in Erlang, so sequencing an effect no longer
    needs an invented name. A plain
    name still generalises; a destructuring pattern binds monomorphically and
    must cover every value of the bound type — a refutable one (Some(x), a
    literal) is a compile error (C0057). Lowers to a one-arm match, so the IR
    and codegen are unchanged.

Changed

  • Actor handlers and init no longer carry return types. A handler's
    outcome is always Next<State> and init always returns the state, both
    fixed by the actor's state: field, so the grammar drops the slot:
    handle Tick, st ! {…} = … and init: fn(c: Cfg) ! {…} = …. Writing a
    → … there is now a parse error (P0006) with a removal hint. The IR
    pretty-printer prints the new form; demos, fixtures, and docs follow.
  • Built-in effect heads need no declaration. Tool, Send, Await,
    Spawn, Schedule, and Exn are pre-declared like Install,
    Supervise, Stand, and Clock, so a module no longer opens with
    effect Tool<t> before its rows may name a tool call or a message send.
    A declaration of a built-in head is accepted with a warning (C0056) and
    otherwise ignored; user heads (effect Audit<t>) are declared as before.
    The IR pretty-printer no longer synthesises declarations for built-in
    heads. The demos, fixtures, and docs drop the redundant lines.

Install

Download the archive for your platform below and extract it; each one
carries three binaries:

  • hird — the compiler CLI (check, build, run, emit-ast, emit-effect-graph)
  • hird-lsp — the language server (diagnostics, hover, go-to-definition)
  • hird-mcp — the MCP server exposing compiler introspection to LLM agents

Or build from source with Rust 1.97+:

cargo install --git https://github.com/no-materials/hird hird-cli

Compiling and running Hirð programs (hird build, hird run) needs
Erlang/OTP on PATH; hird check works without it.

See the README to get started.