Block Expressions
A block evaluates local bindings and expressions in order, then yields its
trailing expression. Default delimits a block with braces; ML uses an indented
layout block. Both lower to Expr::Block { statements, value }.
block ::= "{" statement* expression? "}"
let result = {
let x = 10
let y = 20
x + y
}
result =
x = 10
y = 20
x + y
Evaluation and scope [BLOCK-SCOPE]
- Statements run from top to bottom.
- The trailing expression can use bindings introduced earlier in the block.
- Each block has a child lexical scope. An inner binding may shadow an outer binding; leaving the inner block restores the outer binding.
- A binding introduced in a block is not visible after that block ends.
- An outer binding remains readable inside nested blocks.
let x = 100
let result = {
let x = 50
let inner = {
let x = 25
x
}
x + inner
}
// result is 75; the outer x remains 100
Result value
A block with a trailing expression has that expression's type and value. A
block without one yields Unit.
Discarded values [BLOCK-DISCARD]
A statement runs for its effects and its value is thrown away, so a statement's
type must be Unit. Any other type is a compile error naming the type and
pointing at the statement:
a `int` value cannot be discarded; bind it with `let`, or remove the statement
A discarded Result keeps its own wording, because there the fix is to handle
the error channel rather than to bind the value
(ERROR-RESULT-DISCARD):
an unhandled `Result` cannot be discarded; use `match` or `?:`
The rule reaches every statement position — inside a block, inside a lambda body, and at file scope — because the value is equally lost in all of them. The trailing expression is not a statement and is never subject to it.
Discarding on purpose
Bind to _ when a value genuinely has no use at the call site. The binding is
the author saying the loss is intended:
fn record(sale) ![Db] = {
let _ = perform Db.record(sale)
print("recorded")
}
record sale =
_ = perform Db.record sale
print "recorded"
The check runs against the final substitution, so a type still open where the
statement is written is judged at the type it eventually resolved to. A type
variable that never resolves is accepted: nothing proves it is not Unit.