assert after a struct’s fields, and found where none is writtenOpen and answer this issue on GitHub.
language, ready · opened 2026-10-19 · updated 2026-10-20 · 1 comment
#384 asked whether a field’s bound is “an invariant of the type, or a fact within one method”. ADR-306 D17 (#711) decided the second half: a field is a fact while nothing can change it.
For type invariants, the owner rejected “no case needs it” as an argument. The question was weighed by what is common:
asserts (ADR-269) and loop invariants are found by the compiler (ADR-361).The owner chose C: written and found. The assert goes inside the struct’s body after its fields. A rule in an impl block is not excluded for later.
ADR-369; Part I 4.1; Part III 14.2.
pub struct Piece {
start: i64,
end: i64,
assert(0 <= start && start <= end; message: "a piece ends after it starts")
}
Where a rule must hold. It holds after every construction (a literal, with, or the constructor’s result). It holds at the end of every method that changes the value, at each return and throw. It holds after every write to a field from outside such a method. Each such point is an ADR-269 D4 claim: proved where the compiler can, checked where it cannot.
Where a rule is a fact. Wherever a value of the type is read, the rule is a fact over its field paths, for as long as ADR-306 D17 keeps one.
Found rules. For a struct with private fields that writes no rule, the compiler searches for one with ADR-361’s strategies under the counted budget. A found rule is used only once it is proved. It is recorded in nikaia.proofs. It is never checked at runtime and never published in the ledger.
--asserts lists both kinds.
Depends on #711 (field paths in the walk).
assert(...) in a struct body after the fields; several allowed, each with message:. Struct-body grammar in the parser crate, which is a winnow-grammar DSL.comptime values and literals, and is pure (ADR-269 D3).mut self method, and each outside field write.self at a method’s entry.pub struct is published as part of the type’s contract (ADR-269 D18). Changing it in the breaking direction warns (D19).nikaia.proofs, used only as facts.--asserts (D5).mut self method that breaks it in the middle and restores it passes; one that leaves it broken is caught at its exit;pub field write from another package is proved or checked;Piece;0 <= at <= c.len()) removes a check, and after an edit that breaks it, the check returns and --asserts says why.cmp/phase3/fast/fast.nika with Piece carrying its rule.Built so far (0.0.1155–0.0.1158): D1–D3, and three holes closed. Each hole was shown with a probe that read past the end under remove-bounds-checks:on.
Commits:
self is handed on. It is a fact at each entry. NK1288 refuses a rule that is not a pure claim over the fields.self and throws gets no entry fact. A failing call left the method with the rule broken, and the caller could catch the error and go on.pub field, or a pub type whose fields are all pub or defaulted. A dependency is lowered without its consumers in view, and the consumer had popped a slot;T: …”).Still to build:
pub field or builds a literal. With that, open types could keep their facts.--asserts). #772 builds --asserts for assert and assume if it is not here first.self.slots.len() == N) is not proved yet. Writes to an element should leave .len() unchanged; together with #767 that gives fast.nika’s table index.Generated by Claude Code