String for ref String) breaks callersOpen and answer this issue on GitHub.
ready · opened 2026-10-27 · updated 2026-11-03 · 2 comments
Found while moving code into Nikaia. NK1261 (ADR-282 D2, Part I 2.2, tools/types.nika types_text_by_ref) asks for String in place of ref String on a parameter. But a String parameter that the body reads with ?? is counted as kept (taken by value), and so is a String? parameter read with ?? or match. A caller that uses the text again is then refused. With ref String there was only the warning.
Probe
fn first_comma(line: String) -> i64 { return line.find(",") ?? 0 }
fn main() {
let own = f"a,b{1}"
print(first_comma(own))
print(own)
}
Seen: error[NK2105]: ... passed to first_comma, which keeps it.
Expected: first_comma only reads line, so the parameter is lent. The program prints 1, then a,b1.
Same root, other refusals:
l gives NK1102 (expects String, passing ref String).line[..<at].to_string() after such a call gives NK2302.These bodies already stay views: split, a match on literals, for c in x.scalars, trim, and forwarding to another view parameter.
Why it matters: until this is fixed, following NK1261’s own advice turns its warning into an NK2105/NK1102 error wherever a text parameter goes through ??, and that is a common pattern.
To build
?? (as the left side) or a match stays lent. Test it in tests/compiler/tests/ with the probe above and its .stdout, plus the loop-element case.Generated by Claude Code
Taken by a builder lane of the main session (2026-10-27, from 10:00Z). The driver should not brief it a second time.
Generated by Claude Code
The String half is fixed in 2a04c4d (version 0.0.1236). The cause was not ??: it was the method lookup in the keeps walk (tools/keeps.nika). String::find was not found, because the ledger names text methods str::, so the walk fell back to every find, including Seq::find, which takes its receiver. The probe, the loop element (NK1102), the NK2302 case and the slice case now pass, and the slice lowers to &str (no EitherText).
Still open: a String? parameter that is only read through ??/match is kept on purpose by classify’s rules for ?? and match. For example fn size(line: String?) -> i64 { return (line ?? "").len() }.
To build:
String? the way a written ref String? lowers: Option<&str> with .as_deref() at the call, as lends_option already does for options. Today the inferred one becomes &Option<String>.?? and through a match binder (reached_of, keeps_handed/part_of_a_parameter, keep_receiver), so that only a body that hands it on keeps it. These must keep working (still kept):
fn named2(line: String?) -> String { return line ?? "none" }fn whole(line: String?) -> String { return match line { null => "", t => t } }tests/compiler/tests/.Generated by Claude Code