The Unit & Never Types

Three things in mimas stand in for “no ordinary value here,” and they mean genuinely different things:

  • null — there could be a value, but right now there isn’t. It’s a real value you can hold and test, and it only appears behind an option (T?).
  • (), the unit type — truly nothing. The result of an expression that was never going to hand back a value.
  • !, the never type — a dead end. The “result” of an expression after which no further code can run.

null belongs to Optionals; this page covers the other two.

Unit — ()

Any expression that doesn’t produce a meaningful value evaluates to the unit type, written (). An empty block, a for loop, a function with no return — they all yield ().

let a: () = {};
let b: () = loop { break; };

Never — !

The never type, written !, is the type of an expression that diverges — one after which no code can run. It arises from return, panic, todo, and infinitely-running loop {}. Only the compiler can produce a !; you can never annotate a binding with it directly.

let a = loop {};       // `a` is `!` — the loop never ends, so `a` is never assigned
panic("oh no!");       // `panic` diverges, so it is `!`

What makes ! useful is that it coerces into any type. Because a diverging branch can never actually supply a value, the compiler lets it stand in for whatever type the surrounding code expects. That’s why one arm of an if can bail out while the other still determines the type:

let a: int = if some_condition {
    0
} else {
    return;  // `return` is `!`, so it fits where an `int` is expected
};

The same applies to match: an arm that panics contributes !, which folds into the common type, so it doesn’t force the other arms to become optional.