Open and answer this issue on GitHub.
ready · opened 2026-10-14 · updated 2026-10-28 · 3 comments
A lambda is a function written in place: xs.iter().filter(fn(x) { x > 1 }). A walk (filter, map, any, …) hands its lambda one item at a time. When the items are tuples - a Vec[(String, i64)], a map’s pairs - that one item is a pair:
let pairs: Vec[(String, i64)] = [("a".clone(), 1), ("b".clone(), 2)]
let n = pairs.iter().filter(fn(pair) { let (name, v) = pair
v > 1 }).count()
Authors (and rs2nika’s drafts) write the shorter form, naming the parts as parameters:
let n = pairs.iter().filter(fn(name, v) { v > 1 }).count()
Today (0.0.952) the checker refuses that form with NK1101: This lambda takes 2 arguments, but it is handed one tuple, and the help writes the one-argument form with let (name, v) = pair. Before 0.0.952 it reached rustc, which refused |name, v| with E0593 (lowering-defects D18). So nothing is broken now; the question is whether the short form should mean the destructuring.
docs/lowering-defects.md D18, met when rs2nika drafts of check/mod.rs were run through lower-std (2026-10-11). It is a language question, not a defect: no ADR, spec section or issue decides it (searched: ADR-231 decides how filter’s lambda takes its item, not tuples; Part I’s lambda section names no pattern parameters). Nothing needs the answer now: the refusal names the spelling that works. It is a convenience question.
| Language | Two names for one pair | Spelling |
|---|---|---|
| Rust | refused (E0593) | \|(name, v)\| - a pattern in the parameter |
| Swift | accepted until Swift 4 (SE-0110 removed the implicit splat, then a special case brought it back for closures) | { (name, v) in } |
| Kotlin | refused for Pair |
{ (name, v) -> } - parentheses mark destructuring, distinct from { name, v -> } |
| JavaScript | refused in effect (second parameter is the index) | ([name, v]) => ... |
| Python | refused since Python 3 (PEP 3113 removed tuple parameters) | lambda pair: ... |
Every one of them that allows it marks the destructuring with brackets; none lets the count of parameters decide silently, because fn(a, b) also means “two arguments” for fold and for function values.
let (a, b) = pair in the body. Cost: one line per lambda.fn((name, v)) { .. }, lowering to Rust’s |(name, v)|. Explicit, matches Rust and Kotlin; needs a grammar change (a parameter becomes a pattern) and the checker must type it.fn(name, v) takes a pair apart when the place hands one tuple of that size. Shortest; but fn(a, b) then means two things depending on the item type, and a change of the item type silently changes the meaning (Swift’s SE-0110 story).Option 3 makes a program’s meaning depend on an inferred type; a refactor of the item type turns a destructuring into an arity error or the reverse. Option 2 adds grammar for a convenience. Option 1 costs nothing but a line.
Option 1 now, option 2 if a later round wants it: the refusal with its help is already built (0.0.952) and teaches the working form; bracketed pattern parameters are the only form other languages converged on and can be added later without breaking anything. Not option 3.
(a, b).NK1101.|(a, b)|.filter, map over pairs, a map’s iter(); the refusal of fn(a, b) stays.How it went. The issue recommended option 1 now and option 2 for lambdas later. Two points came up with the owner.
.0), annotations and keyword calls could not reach it, and it was seldom used. None of that holds here. The signature is the ledger’s and is read by the compiler, the parts have names, and options (the only parameters a call names) stay names. As for use: about 171 of roughly 1070 closures in the compiler’s own Rust take a tuple apart (counted by regex), and those are exactly what rs2nika ports.Checked on 0.0.966:
| Form | Result |
|---|---|
fn(name, v) |
NK1101 |
fn((name, v)) |
parse error |
fn(pair) { let (name, v) = pair … }, the form NK1101’s help recommends |
refused by rustc (v is a reference). Filed as #626 |
Decision. Recorded in ADR-358 (D1-D5), Part I 2.1 (Taking a tuple apart) and 5.3, and Part III 13.5 and C.3. Version 0.0.967, commit 4069587.
fn dist((x, y): (f64, f64)), fn((name, v)). A function writes the whole type after it. An option is never a pattern, and a variant is NK1259 (D1).fn(a, b) stays two arguments. NK1101’s help now writes fn((name, ty)) (D2).ref and mut belong to the whole parameter: the parts become views or changeable (D3).signature writes the pattern as written, and every other key names the parts by name. A caller maps a part to its argument’s component (D4).let at the top of the body (D5).fn (with : Type) and of a lambda may be a tuple pattern. (p) is p. A variant there is NK1259, and an option written as a pattern is refused.NK1101 for fn(a, b) over one tuple, with the new help.let taking it apart at the top of the body. Parts of a borrowed tuple are read as the for head reads them (this is the same fix as #626).signature. Every key that names a parameter (keeps, returns, from(p), code, maps, views, touches, sharing) names parts. Readers map a part to the argument’s component.filter(fn((name, v)) { v > 1 }) over Vec[(String, i64)] prints 1;map(fn((k, v)) { … }) over a map’s iter();fn((a, (b, c)));fn dist((x, y): (f64, f64)) called with a tuple;fn pick(ref (a, b): (String, String)) -> ref String returning a or b, with the ledger writing ref(a | b) and a caller in another package reading it;mut (a, b) parameter whose change reaches the caller;fn(Some(x)) is NK1259;fn(a, b) over pairs is still NK1101, with the new help.The commit that builds the last step says Fixes #623 (and #626 if it lands with step 3).
Generated by Claude Code
Met again in rs2nika (#877, R110-R113): a lambda over a Seq of pairs cannot take the pair apart. fn(k, v) is refused with NK1101, and fn((k, v)) does not parse. The converter turns Rust’s .map(|(k, v)| ..) over zip/map entries into fn(k, v), which is not valid Nikaia. Those lines stay wrong until this issue is built.
Generated by Claude Code
Built in 0.0.1262 (5c14b5f): the lambda half. fn((a, b)) { … } and nested fn(((a, b), t)) parse. The pattern becomes one parameter under a hidden __nikaia_tuple<byte> name, and a let at the top of the body takes it apart. A pattern of the wrong width is NK1283 (“A lambda’s parameter takes N parts apart…”). NK1101’s help now writes the pattern out.
Still open: the pattern in a function’s parameter (fn dist((x, y): (f64, f64))), its ledger keys, ref/mut before a pattern (D3, D4), and fn(Some(x)) as NK1259.
Generated by Claude Code