ADR-140: Five Places Where One Thing Had Two Spellings, and Which One Stays

Status: Accepted Date: September 19, 2026 Related: ADR-133 (an options-only argument list), ADR-286 (use brings nothing in), ADR-296 D24 (a grammar is entered by a call), ADR-281 D2 (a hull you can observe, you write), Part I 4.2, 7.1 and 9.1, docs/language-review.md §3.3

1. Context

language-review.md §3.3 found five places where the language says one thing two ways. None of them is a bug and each of them is a reader stopping to ask which they are looking at:

A sixth was on that list and is answered: the options-only argument list is ADR-133.

Five picks in one record, because they are one question — does this language say one thing one way — and answering them apart would migrate the corpus five times for one property.

2. Decisions

D1 — The struct literal is the brace form, and Name(field: value) goes.

let r = Reading { name: "a", temp: 1.0 }
let s = Stats { min: first, max: first }
let t = Summary(first)          // still the anonymous constructor

Name { … } cannot be mistaken for a call, and the shorthand field already lives there. Name(a, b) stays what it is: a call, of the anonymous constructor or of anything else.

With the named literal gone, name(field: value) is a call with options and nothing else, which is the spelling ADR-133 D1’s options-only call needs: execute(target_age: 30) is the same five tokens as Stats(min: first).

The old spelling is told apart by the checker, not the parser. A call carrying options whose callee declares a type is NK1146, which names the braces with the fields the line already has; it is asked before the call is resolved, so it never answers about the anonymous constructor. A name that is both a type and a function cannot be declared (ADR-286), so the question never meets one. A call to a name nothing declares, Widgit(size: 3), is silence, as Widgit(3) is (Part III C.4); the brace form still carries the claim (NK1135).

D2 — A type is constructed by its anonymous constructor, and new leaves std’s own types.

Vec(), String(), HashMap(), not Vec::new(), String::new() and HashMap::new(). The anonymous constructor is what a .nika file writes (pub fn(first: i32), Part I 4.2) and new is Rust’s convention reaching through a hand-written ledger.

One convention, and it is this language’s own. A written Type::new is NK1149, in std as in a .nika file, and the value form goes with it: a constructor passed as a value is Summary, not Summary::new. A qualified name (http::Server::new()) and a name this unit does not declare are left alone (Part III C.4), which is what lets a .nika file name a Rust-side item. The ledger keeps Type::new as its key, because that is the name the lowering writes (ADR-296 D17); what reaches the library is the resolution, so Vec() is one rule with Stats(first), and where a library constructor declares its result (-> Vec[?]) that result is the type.

D3 — A path is ::, everywhere.

Json::value(input) and T::fields, never Json.value(input) or T.fields (ADR-296 D24, Part II 10.3). A grammar’s name is a name and a rule of it is reached the way every other qualified name is reached. The dot is NK1147, raised in the checker, and only where the receiver is a grammar of this file and the name is one of its rules: Json.value(x) and text.value(x) are the same tokens, and a method on a value that shares a spelling is untouched.

The dot is for a value’s members, and keeping it for a namespace was the one place this language asked a reader to tell two things apart by what the left side happens to be.

D4 — throws and sync stand after the result type.

fn f() -> String throws and fn f() -> String sync throws. A sync or a throws in front of a -> is refused with a message that names the order, and sync stands before throws — one slot, in the order ADR-277 D6 gives a function type; throws sync is refused too.

After reads best and is what every example writes — and it is the order ADR-277 D6 already fixed for a function type, where the trailing words are greedy. One order in a declaration and in a type.

A declaration with no result type writes the word where it always did. fn tick() sync { … } has no arrow for the word to be before or after, so nothing about it changes; what goes is a promise standing in front of a ->.

The one shape this takes away. ADR-277 D6’s greedy rule means the trailing words on fn make() -> fn(i64) -> i64 sync belong to the result type, so a function whose result is a function type has no place to write a promise word of its own; its own sync is inferred from its body, as every function’s is.

D5 — use brings nothing in, for std as for a package.

Changed by ADR-313 D3: HashMap is not in the prelude; a program writes collections::HashMap after use std::collections, and the prelude is the closed list of ADR-313 D1.

use std::collections then collections::HashMap. ADR-286’s rule is the language’s, and std follows it too.

What the prelude holds needs no use at all: what goes is use acting differently depending on what follows it. Importing a type — use std::collections::HashMap — is NK1156; a name is a type, not a module, where it has entries called on a value (a self) of its own. use std::fs is a module and stands, and so does a module nothing describes yet (use std::db::postgres), because refusing a surface that does not exist would refuse a correct program (Part III C.4).

3. Consequences

4. What this does not decide

This site is open source. Improve this page.