fn longest(a: String, b: String) -> ref String is NK2302 since D25. Keep the refusal?Open and answer this issue on GitHub.
language, decision · opened 2026-10-12 · updated 2026-10-12 · 0 comments
A function can hand back a view of one of its arguments instead of a copy:
fn first_word(text: String) -> ref String { text.split(' ')[0] }
The result points into text’s buffer, so it is valid as long as text is. Nikaia writes no lifetimes; the compiler works out which argument the result points into. That is easy with one source. With two it is a question:
fn longest(a: String, b: String) -> ref String {
if a.len() > b.len() { return a }
return b
}
Which buffer does the result point into? It depends on a run-time comparison. The caller cannot know, so it must keep both a and b alive as long as it uses the result. That is what the language did until 0.0.836 (parameters lowered as views shared one lifetime: “union”), silently: every caller met the constraint at its own line.
#580 (ADR-283 D25, written by the Opus review of the owner’s note on NK2302, 2026-10-11): a result views exactly one source over all ways out; two sources chosen at run time are refused with NK2302. Part I 6.5 says ref on a parameter is only an assertion, so longest(a: String, b: String) and longest(a: ref String, ...) follow the same rule. This took back something that compiled before. It is isolated in crates/nikaia/tests/result_source.rs::plain_string_longest_is_refused_since_d25 (the doc comment says how to revert). One existing test program had the shape (tests/language/src/views_handed_back.nika, views_handed_back_first) and was rewritten to return ref a or a literal, same output.
ref a or a copy (a.clone()), or split in two functions.y” for a function that returns only literals).payloads, options_of and two more); 0 of the 17 in check/+emit/ need the union.ref and plain parameters; costs a rewrite where a run-time choice is real.-> ref(a | b) String, the spelling the ledger already uses for ref(self | other)): runs against ADR-283 “nothing is written”; every caller must keep both buffers.Keeping the refusal: a user with a real run-time choice must clone or split. Reverting: callers fail far from the definition.
Keep the refusal (Part I 6.5 and 6.6 are then consistent, and the owner’s note chose a hard stop for run-time ambiguity). Revisit only with an explicit marker if a measured cost of the clone shows.
Related: #580, #579 (the importer meets this shape often), docs/mitigation-hurdles.md section 1.