ADR 0016: Type tests are type assertions, with smart casts
Status: Accepted; smart casts of locals amended by 0031
Context
Section titled “Context”Go code inspects dynamic types with type assertions (v, ok := x.(T)) and
type switches. Kotlin has is, !is, as, as?, and smart casts. Lyth needs
the capability (optional interfaces such as if (store is pinger), decoding
Any values, error types) without hiding what the Go code does.
Decision
Section titled “Decision”x is T,x !is T,x as T, andx as? Trequire an interface-typed operand and a non-nullT. They lower to type assertions:aspanics likex.(T), andas?yields null (t, _ := x.(T), boxed for value types). Impossible tests (a concrete type that does not implement the interface) are compile errors, as in Go.- A successful test smart-casts an immutable variable, like a null check. The
generated code binds the asserted value once, in Go’s idiom:
if nf, ok := err.(*NotFound); ok,switch s := s.(type), andnf, ok := err.(*NotFound); if !ok { return }. Awhenwhose conditions are allistests is a type switch. - Type tests see the dynamic type only. They do not unwrap wrapped errors; for
errors, use
errors.AsType<T>(err)(Go 1.26) orerrors.Is.
Alternatives
Section titled “Alternatives”isunwraps errors (errors.Assemantics): convenient for errors but surprising for everything else, and a hidden behavior difference from Go.- Smart casts as repeated assertions (
x.(T)at each use): simpler to generate, but not what Go programmers write; bindings cost nothing and read naturally. - No
as:as?plus!!covers it, butasmatches the cost and behavior ofx.(T)exactly.
Consequences
Section titled “Consequences”Generated code is idiomatic and allocation-free (assertions on interfaces).
Mixed when conditions (is together with values, or conditions that need
statements) lower to if/else chains so each condition is evaluated only
when reached. Binding names come from the tested type (circle, nf) and
avoid every user and package-level name in the function.