; — the Leading Semicolon Is Gone, and It Is Not OptionalStatus: Accepted
Date: September 19, 2026
Related: ADR-296 D5 (the typed spread after the ;), ADR-108 (a required argument is positional), ADR-140 D1 (the named struct literal goes, so name(field: value) is only a call), Part I 5.1 (the subject ; config protocol)
Part I 5.1 separates a call’s subjects (positional, before the ;) from
its options (named, after it), and a function whose only parameters are
options was called query.execute(; target_age: min_age): a semicolon with
nothing before it, a shape no reader has seen in any language. The
specification itself wrote the call both ways — 17.1 has
io::read_to_string(trusted: false) — which said what the eye already knew.
The ; earns its place where it separates two zones. Where there is only one
zone it separates nothing. The question was whether removing it costs the
parser anything, because an option is name: value and a subject is an
expression, and the two would now meet in one list.
;, and so does a signature whose parameters are all options.fn read_to_string(path: &str; trusted: bool = false) -> String // two zones: `;`
fn execute(target_age: i64 = 0) -> Vec[User] // one zone: none
io::read_to_string("in.txt"; trusted: true) // two zones: `;`
query.execute(target_age: min_age) // one zone: none
The rule is what it always was — a subject is positional, an option is named,
an option has a default (ADR-108 and Part I 5.1) — and the ;
stands between the two zones. Where one zone is empty there is nothing to
stand between, and the semicolon is not written.
; is refused, so there is one spelling.f(; opt: 1) and fn f(; opt: i32 = 1) are parse errors whose message says
an argument list with no subjects writes its options without the ;:
f(opt: 1). Two accepted spellings would be worse than either alone: the
old one would keep appearing in code that reads the old pages, and a reader
would wonder what the difference is. There is none, so there is one form.
A mixed call keeps its ; and keeps it required: request(url, timeout: 60)
stays refused, because the ; is what makes positional
before, named after visible without counting parameters.
Nothing in expression position begins with a name followed by a colon — a
struct literal begins Name { (the named literal Name(field: value) is gone,
ADR-140 D1), a path name::, a call name(, and there is no
type ascription — so name: at the head of an argument list is options and
nothing else. The argument-list rule gains one alternative before the present
one: a list of options, tried first, which succeeds or fails on the second
token. The signature rule gains the same: a list of parameters with
defaults, tried first and decided after the type, because it insists on the
= value that makes a parameter an option; a plain name: T before it is an
ordinary subject.
The typed spread ...args: Self::dsl of ADR-296 D5 keeps its
; in the signature, pub fn execute(&self; ...args: Self::dsl), because
it stands after a subject there and is a zone of its own. At the call the
receiver stands outside the parentheses, so the zone before the ; is empty
and the call is query.execute(target_age: 30), like any other options-only
call.
No AST changes: the call already carries subjects and options apart, and the emitter and the checks read those. A method call’s options are filled from the declaration’s option order, as a call by name’s are.
execute(target_age: 30).request(url: "…")) or positional options: the
zones stay what they are.; in a mixed call in favour of named after positional
by convention; the ; is what the language chose to make the rule visible,
and this record does not reopen it.