ADR-133: An Argument List of Options Alone Writes No ; — the Leading Semicolon Is Gone, and It Is Not Optional

Status: 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)

1. Context

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.

2. Decisions

D1 — A call whose arguments are all options writes them without a ;, 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.

D2 — The leading ; 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.

D3 — It costs the parser one alternative, tried first.

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.

3. Consequences

4. What this does not decide

This site is open source. Improve this page.