Blocks & Scope

Like Rust and Zig, a block in mimas is an expression. Curly braces group statements, and if the last thing inside — before the closing brace — is a bare expression (no trailing ;), the block evaluates to it.

let a = {};          // -> ()  (no trailing expression)
let b = { 0 };       // -> 0
let c = {
    let x = 1;
    let y = 2;
    x + y            // no semicolon: this is the block's value
};                   // -> 3

Because blocks are expressions, the same rule powers if, match, and loops — they can all produce values.

Scope

mimas is lexically scoped: a block defines a scope. Code can read and reassign variables from any block that encloses it, but not the other way around. When a block ends, the variables it declared fall out of scope.

let a = 0;
{
    let b = a; // valid — `a` is visible from the enclosing scope
}
let c = b;     // compile error: `b` is not defined out here

A let inside a block is a brand-new binding. It does not disturb a same-named variable in an outer scope — that’s shadowing. Reassignment (=, without let), on the other hand, reaches outward to the existing variable.

let a = 0;
let b = 0;
{
    a = 1;     // reassigns the outer `a`
    let b = 1; // new binding, shadows the outer `b` only inside this block
}
// -> a is 1, b is 0