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