ADR-182: A for Lends, and Everything Downstream of the Binding Has to Know It

Status: Accepted Date: September 21, 2026 Closes: #138 (a cast over a for binding is a cast over a view) and #147 (a slice of text read as a value does not lower) Builds: ADR-094 D4 (a for lends) and ADR-293 D9 (a read is a call and the * takes it apart), neither of which changes — this is what they already said, said at the two places that had not heard it Related: ADR-285 D12 (a narrowing conversion aborts), ADR-285 D1 (a length is an i64), ADR-152 D4 (an array carries its length), ADR-179 D1 (ref Array[T] is a run), ADR-185 D1 (a let that declares a struct over a lent binding), ADR-288 (the emitter knows no types)


1. Context

Two constructs left Part III C.1 to the language below, speaking about the generated file (#138):

comptime NS: Array[i32, 3] = [1, 2, 3]
for n in NS { sum = sum + (n as i64) }     // casting `&i32` as `i64` is invalid
println(f"{text[1..3]}")                   // the size for values of type `str`
                                           // cannot be known at compilation time

Neither noun — &i32, str — is in the program, and both came with a way out that cannot be taken: dereference the expression, the trait Sized is not implemented for str. C.2 says a way out that cannot be taken is not one.

What makes them one record is that both are a view the compiler wrote meeting a construct that was never told. A for lends (ADR-094 D4), so the binding is a &i32; a read is a call whose * takes it apart (ADR-293 D9), and over a range what it takes apart is a &str. Each decision is right and neither is wrong here: what is needed is the sentence that says so at the cast and at the bracket — and at the three further places D3 to D5 name, each of which would otherwise be a silent wrong answer.

2. Decisions

D1 — A cast’s operand is read through the view, and by a trait rather than by a *.

nikaia_std::num::value(n) as i64, where value is the identity for a number that is already a number and the number behind any run of views.

A * would be right at the binding and wrong one line down. let n = 5 inside the loop shadows it, and a * written onto that is rustc about the generated file — the very thing this closes, moved rather than removed. Choosing on the type is what a trait is, and this is index::At’s own reasoning one construct over: a uniform rule is the one that cannot be applied to the wrong operand.

Which operands is the checker’s, because which name is a view is a question about the scope and the emitter keeps none (ADR-288). It is recorded on the binding — Local::lent — for the reason immutable is: the scope is then the one the checker already keeps, and a let that shadows the name stops being a view exactly where it stops being the answer.

Only a bare name, because that is the only operand shape that can arrive as a view: a call, a literal and an arithmetic expression each come to a value, and a field read or an index is taken apart by the lowering that wrote it.

Inside the checked conversion and not around it. A narrowing cast over a lent binding is still a narrowing cast, and the abort ADR-285 D12 puts there is not a thing a view may skip.

D2 — A read at a range answers a view, so it takes no *.

text[1..3] and xs[1..3] answer a &str and a &[T]; * over one of those is a str or a [T], which the language below has no size for. The read at a number keeps its *, because xs[0] is the element the program asked for and not a view of it.

Off the shape of what is in the brackets, which is the one place this needs no type: a range is a run and a key is not, in this language and in the one below alike. So ADR-288 holds — nothing here asks what text is.

And a written ref over such a read is the view it already is. ref dna[i..<i + k] would otherwise be a &&str below: the source’s ref and the read’s own view are one claim written twice.

D3 — A range written in literals settles itself, and one computed does not.

index::at cannot pin a bare 1..=2: At is implemented for a range of every signed type and all of them answer the same RangeInclusive<usize>, so there is nothing to infer from and what came back was cannot infer type about the generated file. Handed over as written it settles itself, because RangeInclusive<usize> is the only candidate that is a SliceIndex<[V]>.

A range built out of names is an i64 one and has to be converted, which is ADR-285 D1’s whole trade. So the literal exception the write branch already had applies to a range too, which it could not while the * stood over one.

Except where it counts from the end. xs[-2..-1] is an access out of bounds and reports as one at run time, which is D1’s own rule — and it has to reach run time. Handed over as written it does not: -2 against the usize a slice wants is the trait Neg is not implemented for usize, about a type the program never named, with consider specifying an integer type that can be negative as the way out. So a negation goes back through the conversion, widened, because an i64 is the one width this language indexes with and at has nothing else to read it off. What the program then gets is index out of bounds: the index is -2, at the line that wrote it.

D4 — An array’s element is its first argument, and a slice’s is its item.

element_of answers for Array[T, N] — whose two arguments are why a Vec arm does not reach it — and for ref Array[T], as it does for Vec[T], List[T] and Seq[T]. Otherwise a for over either binds a name of unknown type, and everything downstream goes quiet with it.

Including ADR-285 D12’s abort: for n in NS { n as u8 } over an Array[i64, 2] would truncate silently, which is the one thing that record exists to stop. An absent type is not an error (ADR-024 D1) and that is right — but an absent type that should have been there is every check downstream of it not run.

D5 — A let whose annotation is a number reads it through the binding; a let whose annotation is a type does not.

let q: i64 = n over a lent n would be &i64 where an i64 was declared. The number is read through the view, by D1’s own helper.

A let copy: Row = r is a different question, decided by ADR-185 D1. A number is Copy, so reading one through a view inserts nothing; a struct is not, so the same answer there would be a copy the source did not write — ADR-283 D3 forbids one in as many words. The way out exists and the program can take it (let copy = r), which makes it a refusal rather than a lowering.

3. Consequences

4. What this does not decide

This site is open source. Improve this page.