ref String largely disappears: String is a view unless a copy is cheaper; the ledger states the public interface; ref stays only at the C boundaryOpen and answer this issue on GitHub.
ready · opened 2026-10-16 · updated 2026-10-17 · 2 comments
While fixing the guide’s examples (fn greet(name: ref String; ...) → String, 0.0.974), the owner asked what ref String is still needed for.
Today (Part I, Text): wherever a program declares String, the compiler decides per value whether it is a view or text of its own (ADR-282). There is never a hidden copy: where a view does not suffice, the compiler refuses and names .clone(). ref String is “the same text with a promise attached: a borrowed view, no copy”. In private code that promise changes nothing in the code emitted. Only one rule of the specification still needs the word: “a published field, parameter or result is never the third” (that is, never “either, per value”). That makes ref String the way to say “view” at pub, and it is required for views in struct fields (struct Reading { name: ref String }, NK2302).
String is always a view, and text of its own only where that is cheaper. The language promises no hidden copy, so ref as a promise is redundant.pub too, no ref: the ledger states what the public interface can do. Whether a result is a view and which parameter it points into, and whether a parameter is lent or kept, are read off the body and recorded in nikaia.contracts. A change shows up in review as a ledger diff. ADR-094 and ADR-353 already work this way for parameters and results.extern and pub extern fn the signature is therefore written (ref String, ref Array[u8]) and is required. The compiler checks the body against what is written, rather than deriving what is written from the body.ref String inside Nikaia is accepted for now and gets a warning (new NK code: “ref is redundant here, the ledger records it”). It becomes an error later. tools/*.nika, the examples and the specification lose it step by step.| ADR | Why |
|---|---|
| ADR-282 | Text is one type, decided per position. Holds the “published is never the third” rule that falls here |
| ADR-283 | Tethers and NK2302: a view that escapes. What a struct field carries without ref |
| ADR-353 | A result’s views point where the body says: already ledger-based. Check that it covers fields |
| ADR-094 | Lent or kept is the callee’s, inferred into the ledger: the pattern this extends |
| ADR-202 | A view of the subject handed back without ref: the precedent |
| ADR-186 | A parse keeps the text its record views: records with view fields |
| ADR-302, ADR-284 | C boundary: what crosses is a view for the call. The exception in point 3 has to agree with them |
| ADR-311 | Build-time values cross as views (ref Array[T]): does the same apply to ref Array? |
Open: does the same direction apply to ref Array[T] and other ref T outside the C boundary, or only to text?
A deciding session writes the ADR (a new D in ADR-282, or a new record) and the specification text. That includes the warning’s NK code in Part III, and the C-boundary rule checked against ADR-302 and ADR-284. It then adds a numbered To build list here, with tests.
ref on scalars (owner’s direction: for all scalar types)Today. For a value the size of a machine word (i8…i128, u8…u128, isize/usize, f32/f64, bool, scalar), ref is handled inconsistently:
let n: ref i64 = counts[0] is refused with NK1257 (Part I, just before “Nothing here inserts a copy”). The reason given: such a value is copied at a read, so there is no view of it to declare.fn f(x: ref i32) -> i32 { return x + 1 } compiles, and f(2) prints 3 (checked on 0.0.974).tools/*.nika and examples/ contain no ref i32 or ref i64.Direction. ref on a scalar type says nothing: the copy is always cheaper than a pointer. So it is refused outside extern "C" everywhere (parameter, field, result, let) with NK1257, not just at let. Nothing uses it, so no warning phase is needed first. A parameter the function is meant to change stays mut name: T (ADR-094 D3).
Exception: the C boundary, as for ref String. In an extern "C" declaration, ref T and ref mut T are the pointer C expects (ADR-302 D5): ref i32 is const int32_t *, and ref mut i32 is int32_t *, for example an out-parameter such as int *len. Each lives for the call. It is not a raw pointer (D9). This stays, written out and checked.
ADRs to check, in addition:
mut instead of ref mut)NK1257: its rule widens from let to every positionTo build (for the deciding session to refine):
NK1257 cover a ref on a scalar type at a parameter, field and result, with a test for each position.extern "C" with ref i32 and ref mut i32 stays accepted.Generated by Claude Code
Decision (3484280, owner’s direction above):
ref String adds nothing. The ledger states the public interface. ref String outside the C boundary is the warning NK1261, and it becomes a refusal once the specification, examples/ and tools/*.nika no longer use it.extern "C" block, a pub extern fn) a view is written, required, and the body is held to it.ref on a word by its kind (a number, bool, scalar, or an enum whose variants hold nothing) is NK1257 at every position outside extern "C". In an extern declaration, ref i32 / ref mut i32 stay.pub extern "C" fn to pub extern fn, the spelling of ADR-324 D6.ref T (ref Array[T], ref Vec[T], structs). It is listed in ADR-282’s open points. Nothing needs the answer yet.Built (2e20ee1): NK1257 at a parameter, a field and a result, including a trait method’s parameter and result, and on bare enums. It was built in tools/types.nika and tested in tests/element_views.rs:
a_word_declared_ref_is_refused_at_every_positiona_word_declared_ref_at_the_c_boundary_staysThe enum parameters in tools/*.nika and tests/language lost their ref. Lowering defect D42 is filed in docs/lowering-defects.md: an untyped let x = m[k] ?? return over a map of words keeps a &T.
NK1261I implemented the warning: parameter, field, result and let, outside extern "C" and pub extern fn, with tests. Then I backed it out, for two reasons:
ref String and assert that no warning is produced.ref from those fixtures changes what the program does today. Dropping ref String → String broke about 40 of them in other ways, in build_time, grammar_at_build_time, bytes, comptime_compiled, dsl_parameters, jump_expressions, nullable, project and typecheck. The affected cases include:
So the compiler does not yet meet D2’s premise that String does whatever ref String does. A warning that tells people to drop ref would point them at programs that then behave differently, or are refused.
String without ref is not what ref String is, make String behave the same, with a test per position. The failing cases above are the inventory: run the suite with ref String → String in the fixtures and collect what changes.NK1261. A ready patch with its tests exists and can be re-done from this description:
types_text_by_ref in types.nika, next to types_word_by_ref, with warning: true;a_ref_string_inside_nikaia_is_a_warning_at_every_position and a_ref_string_at_the_c_boundary_is_no_warning.examples/, the specification’s examples (then regenerate tests/specification/EXPECTED.txt) and tools/*.nika.NK1261 a refusal (ADR-282 D2).The label changes from decision to ready: the decision is taken, and step 1 is building work.
Generated by Claude Code