5.1.48. Compile-Time Field Iteration with apply
This tutorial covers daslib/apply — a call macro that iterates
struct, tuple, and variant fields at compile time. Unlike runtime
RTTI walkers, apply generates specialized code per field with
zero reflection overhead.
Prerequisites: basic daslang knowledge (structs, tuples, variants).
options gen2
options rtti
require rtti
require daslib/apply
require daslib/strings_boost
5.1.48.1. Basic struct iteration
apply(value) $(name, field) { ... } visits every field of a struct.
name is a compile-time string constant with the field name, and
field is the field value with its concrete type:
struct Hero {
name : string
health : int
speed : float
}
let hero = Hero(name = "Archer", health = 100, speed = 3.5)
apply(hero) $(name, field) {
print(" {name} = {field}\n")
}
Output:
name = Archer
health = 100
speed = 3.5
5.1.48.2. Compile-time dispatch with static_if
Because name is known at compile time, static_if can branch
on it. Only the matching branch compiles for each field — the others
are discarded entirely:
struct Config {
width : int
height : int
title : string
fullscreen : bool
}
let cfg = Config(width = 1920, height = 1080,
title = "My Game", fullscreen = true)
apply(cfg) $(name, field) {
static_if (name == "title") {
print(" Title (special handling): \"{field}\"\n")
} else {
print(" {name} = {field}\n")
}
}
You can also dispatch on the type with typeinfo stripped_typename(field).
5.1.48.3. Mutating fields
Pass a var (mutable) variable and apply gives mutable
references to each field:
struct Stats {
attack : int
defense : int
magic : int
}
var stats = Stats(attack = 10, defense = 5, magic = 8)
apply(stats) $(name, field) {
field *= 2
}
// stats is now Stats(attack=20, defense=10, magic=16)
5.1.48.4. Tuples
apply works on tuples too. Unnamed tuple fields are named
_0, _1, etc. Named tuples use their declared names:
let pair : tuple<int; string> = (42, "hello")
apply(pair) $(name, field) {
print(" {name} = {field}\n")
}
// _0 = 42
// _1 = hello
let point : tuple<x : float; y : float> = (1.0, 2.0)
apply(point) $(name, field) {
print(" {name} = {field}\n")
}
// x = 1
// y = 2
5.1.48.5. Variants
For variants, apply visits only the currently active
alternative. The block fires for the one alternative that is set:
variant Shape {
circle : float
rect : float2
triangle : float3
}
let s = Shape(circle = 5.0)
apply(s) $(name, field) {
print(" Shape is {name}: {field}\n")
}
// Shape is circle: 5
5.1.48.6. Generic describe function
apply is ideal for building generic utilities that work on any
struct without knowing its fields in advance:
def describe(value) {
var first = true
print("\{")
apply(value) $(name, field) {
if (!first) {
print(", ")
}
first = false
static_if (typeinfo stripped_typename(field) == "string") {
print("{name}=\"{field}\"")
} else {
print("{name}={field}")
}
}
print("\}")
}
This prints any struct in {field=value, ...} format without
writing type-specific code.
5.1.48.7. Field annotations (3-argument form)
Struct fields can carry metadata via @ annotations:
struct DbRecord {
@column="user_name" name : string
@column="user_email" email : string
@skip id : int
@column="age" age : int
}
Annotation syntax:
@name— boolean (defaults totrue)@name=value— integer, float, or bare identifier (string)@name="text"— quoted string
The 3-argument form apply(value) $(name, field, annotations)
receives annotations as array<tuple<name:string; data:RttiValue>>
for each field. RttiValue is a variant with alternatives
tBool, tInt, tFloat, tString, etc.
apply(record) $(name : string; field; annotations) {
var column_name = name
var skip = false
for (ann in annotations) {
if (ann.name == "skip") {
skip = true
} elif (ann.name == "column") {
column_name = ann.data as tString
}
}
if (!skip) {
// use column_name and field ...
}
}
This pattern powers daslib/json_boost’s @rename,
@optional, @enum_as_int, @unescape, and @embed
field annotations.
5.1.48.8. Full source
The complete tutorial source is in
tutorials/language/48_apply.das.
Run it with:
daslang.exe tutorials/language/48_apply.das
See also
Full source: tutorials/language/48_apply.das
Apply reflection pattern — apply call macro reference.
Runtime type information library — RttiValue variant type used by the
3-argument annotation form.
Data Walking with DapiDataWalker — runtime data walking with
DapiDataWalker (Tutorial 47).
Previous tutorial: Data Walking with DapiDataWalker
Next tutorial: Async / Await