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
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:
Stats(min: first, max: first) and Reading { name, temp } both build a
struct — and Stats(first) calls the anonymous constructor, so Foo(x: 1)
goes round the constructor’s invariants and Foo(1) goes through them,
told apart by a colon;Type::new() in std;Op::Times and Json.value(input);throws and sync stand on either side of -> and the parser takes both;use brings no name in for a package and brings HashMap in for
std.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.
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).
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.
::, 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.
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.
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).
language-review.md §3.3 counted.sync, keeps, touches or
locks, the caller is answered on doubt, not promised.examples/, std and the specification’s pages.pub fn(first:
i32) — which D2 keeps as it is.use and not about what needs none.