11.2. Apply reflection pattern
The APPLY module provides the apply macro for iterating over struct, tuple,
and variant fields at compile time. Each field is visited with its name and
a reference to its value, enabling generic per-field operations like
serialization, printing, and validation.
All functions and symbols are in “apply” module, use require to get access to it.
require daslib/apply
Example:
require daslib/apply
struct Foo {
a : int
b : float
c : string
}
[export]
def main() {
var foo = Foo(a = 42, b = 3.14, c = "hello")
apply(foo) $(name, field) {
print("{name} = {field}\n")
}
}
// output:
// a = 42
// b = 3.14
// c = hello
When the block has no function-escaping return, it runs inline once per field — no helper function
and no per-field block invoke — so it is cheap enough for hot paths like serialization. A block that
uses return to skip a field falls back transparently to a generated per-field helper, where
return keeps its original block-local “skip this field” meaning; nothing the caller writes changes.
apply_imm is a struct-only, slightly faster variant (it aliases the block params rather than binding
reference locals); use it for hot struct field walks and apply for tuples, variants, a
side-effecting source value, or a block that uses return (apply_imm always inlines, so it
cannot accept one).
See also
Compile-Time Field Iteration with apply — comprehensive tutorial covering structs,
tuples, variants, static_if dispatch, mutation, generic
describe, and the 3-argument annotation form.
11.2.1. Call macros
- apply_imm
Struct-only, faster sibling of apply for a hot field walk (e.g. a serialization materializer).
Like apply it inlines the block body once per field, but it aliases the block params via
assume (name -> the field-name string, field -> the self.field lvalue, optional
annotations -> static RTTI) rather than binding a real let/var field & reference local — no
per-field local, so it is ~25% faster than apply under the interpreter (identical under JIT; see
benchmarks/micro/apply_vs_apply_imm.das). It handles structs only and re-reads self per
field, so use apply for tuples/variants or a side-effecting/rvalue self. Because it always
inlines it has no invoke fallback, so its block may not use return (use apply for that).
Same $(name, var field[, annotations]) block shape.
- apply
Visits every field of a struct, tuple, or variant with a per-field block. The block is
$(name, field) — name is the field-name string (a compile-time constant, so
static_if (name == "...") folds) and field is a reference to the field value (read or
mutate; writes propagate to the source). A struct may use a third annotations param receiving
array<tuple<name:string; data:RttiValue>> per field. A variant runs only its active arm.
The body inlines directly into the caller when it has no escaping return; a block that uses
return to skip a field transparently falls back to a generated per-field helper (where
return keeps its block-local “skip this field” meaning). Either way the source is unchanged.
struct Bar {
x, y : float
}
apply(Bar(x=1.0, y=2.0)) $(name, field) {
print("{name} = {field} ")
}
Would print x = 1 y = 2