18. Comptime: one tier, no macros

Brahms sat on his First Symphony for twenty-one years. By the downbeat, the work was already done.

Part 3 ran on the interpreter, because the question was what a program means when more than one thing is happening, and the scheduler is the thing that answers it. This part’s prompts are the compiler’s. The subject here is work that happens before your program starts — during compilation, by the compiler, in a sandbox — so the implementation that evaluates it is the one that compiles. The interpreter reads a comptime fn and hands it straight back.

One idea holds the chapter together, and it is smaller than the feature list suggests: comptime is wolf, evaluated during compilation. Not a second language, not a template dialect, not a macro expander over tokens. The same while, the same if, the same checked arithmetic, the same assert — running on a different clock, inside a box with nothing in it.

18.1 Wolf at compile time

The shelf shards its index across a power of two, and the mask that picks the shard is a number nobody should be computing at startup:

comptime fn shard_mask(shards: int) -> int {
    var mask = 1
    while mask < shards {
        mask = mask * 2
    }
    mask - 1
}

comptime fn expect_mask(shards: int, want: int) -> bool {
    assert(shard_mask(shards) == want)
    true
}

const SIXTEEN_SHARDS: bool = expect_mask(16, 15)

fn test_the_mask_selects_four_bits() {
    let slot = 1234 % 16
    assert(slot == 2, "1234 lands in slot 2 of sixteen")
}
$ wolf test ./shard_test.lu
test ./shard_test.lu::test_the_mask_selects_four_bits ... ok
wolf test: 1 passed; 0 failed; 0 unsupported; 0 filtered out

Read shard_mask first and notice how little there is to read. A var, a while, a multiplication, a subtraction: the loop you would have written anyway. The only word that is new is comptime, and all it changes is when.

Then read expect_mask, which is the interesting one. It computes the mask and asserts the answer, and because it runs during compilation, its assert is a claim the compiler settles. Change the expected number and the build stops:

comptime fn shard_mask(shards: int) -> int {
    var mask = 1
    while mask < shards {
        mask = mask * 2
    }
    mask - 1
}

comptime fn expect_mask(shards: int, want: int) -> bool {
    assert(shard_mask(shards) == want)
    true
}

const SIXTEEN_SHARDS: bool = expect_mask(16, 14)

fn test_the_mask_selects_four_bits() {
    let slot = 1234 % 16
    assert(slot == 2, "1234 lands in slot 2 of sixteen")
}
error[E0710]: this comptime assertion failed
  --> ./s2.lu:13:5
   |
13 |     assert(shard_mask(shards) == want)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluated to `false` at compile time
...
17 | const SIXTEEN_SHARDS: bool = expect_mask(16, 14)
   |                              ------------------- while evaluating `expect_mask`, entered here
   |                              ------------------- while evaluating `SIXTEEN_SHARDS`, entered here
   |
   = note: a failed comptime `assert` stops compilation — it is the witness mechanism for facts the
     checker cannot see on its own.

There is no program. Nothing has been linked, nothing has run, and the loop above has already gone around four times to disagree with you. That is the whole mental model, and the rest of this chapter is its consequences: a witness — a comptime assert whose failure is a compile error — is how a compile-time fact gets a location in your source.

Arguments must already exist

Comptime code runs before the program, so it cannot see values the program has not produced. The rule has one sentence and the compiler recites it:

comptime fn shard_mask(shards: int) -> int {
    var mask = 1
    while mask < shards {
        mask = mask * 2
    }
    mask - 1
}
fn main() -> !int {
    let shards = 16
    const MASK = shard_mask(shards)
    if MASK == 15 { 0 } else { 1 }
}
error[E0705]: `shards` is a runtime value, so this cannot evaluate at compile time
  --> ./s3.lu:13:29
   |
13 |     const MASK = shard_mask(shards)
   |                  ------------------ while evaluating `main`, entered here
   |                             ^^^^^^ must be comptime-known
   |
   = note: a `comptime fn` runs during compilation: every argument must be a literal, a `const`, a
     type, or the result of another comptime call.

let shards is a runtime binding. It will hold 16 when the program runs, and the program is not what is running. Spell it const shards = 16 and the argument exists early enough to be an argument. The note lists the four things that qualify — a literal, a const, a type, or the result of another comptime call — and the list is worth memorizing, because it is also the answer to “how far up does comptime reach?” As far as its arguments do, and no further.

Same rules, earlier clock

Chapter 3 taught what happens when an i32 computation leaves the range of an i32: the program traps, in every profile, because checked arithmetic is not a debug feature. Size the index for a hundred thousand documents at compile time and there is no program to trap:

comptime fn index_bytes(docs: i32, per_doc: i32) -> i32 {
    docs * per_doc
}
fn main() -> !int {
    const BYTES = index_bytes(100000, 30000)
    if BYTES == 0 { 1 } else { 0 }
}
error[E0706]: this `*` on `i32` faults at compile time: 100000 * 30000 leaves `i32`'s range
 --> ./s4.lu:5:5
  |
5 |     docs * per_doc
  |     ^^^^^^^^^^^^^^ checked arithmetic, comptime included
...
8 |     const BYTES = index_bytes(100000, 30000)
  |                   -------------------------- while evaluating `index_bytes`, entered here
  |                   -------------------------- while evaluating `main`, entered here
  |
  = note: checked arithmetic has one semantics everywhere (X3): what would trap at runtime is an
    error at comptime — intended wraparound is spelled `wrapping[T]`, never a mode.

One semantics, two moments. The trap you would have seen at runtime is an error you see at compile time, and the diagnostic cites the same decision the runtime trap cites. This is the cleanest evidence that comptime is not a separate dialect with its own arithmetic: the rule did not change, the clock did.

Exercise 18-1 (comprehension · wolf) — One binding keeps this program out of compile time:

comptime fn double(n: int) -> int {
    n + n
}
fn main() -> !int {
    let x = 21
    const Y = double(x)
    if Y == 42 { 0 } else { 1 }
}

Predict the compiler’s verdict, and name the one-character change that fixes the program. What may an argument to a comptime fn be?

Exercise 18-2 (comprehension · wolf) — Chapter 3 taught you what 2147483647 + 1 does at runtime. Predict what it does inside a comptime fn, and predict the decision the diagnostic cites:

comptime fn brim() -> i32 {
    let big: i32 = 2147483647
    big + 1
}

18.2 Types as values

Here is a function signature that says the whole section in four tokens:

comptime fn field_count(T: type) -> int

A type arrives as an argument. Not as a parameter in angle brackets, not as a template placeholder — as a value, of type type, passed the way 900 is passed. typeinfo is the function that describes one, and what it returns is a value like any other: a name, a kind, a list of fields with names and types of their own.

The shelf’s document type has three fields, and the shelf would like that to stay true:

struct Doc { title: str, words: int, shelf: int }

comptime fn expect_fields(T: type, n: int) -> bool {
    assert(typeinfo(T).fields.len == n)
    true
}

const DOC_SHAPE: bool = expect_fields(Doc, 3)

fn test_a_doc_carries_its_count() {
    let d = Doc { title: "regions", words: 900, shelf: 2 }
    assert(d.words == 900, "the count survived construction")
}
$ wolf test ./shelf_test.lu
test ./shelf_test.lu::test_a_doc_carries_its_count ... ok
wolf test: 1 passed; 0 failed; 0 unsupported; 0 filtered out

expect_fields is §18.1’s witness pointed at a type instead of a number. Add a field to Doc and the count is wrong; the compiler walks the field list, compares, and stops the build with the assertion that failed. The fact is stated once, in the source, next to the type it is about — which is the difference between a shape you rely on and a shape you hope for.

One line in that program is a boundary worth naming. typeinfo answers questions the type checker can already answer: what the fields are called, what types they hold, what kind of thing the type is. It does not answer where those fields land in memory. Ask for a size and the compiler declines, because layout — offsets, padding, the target’s ABI — belongs to the code generator, and a comptime that guessed would have to be right forever. Exercise 18-4 is that refusal.

The derive class, without a macro

The place other languages reach for macros is derivation: you have a struct, you want the obvious equality, and you do not want to type it. Wolf’s answer starts from the fact that the obvious equality is four lines of ordinary code:

trait Eq {
    fn eq(a: Self, b: Self) -> bool
}

struct Pair { a: int, b: int }

impl Eq for Pair {
    fn eq(a: Pair, b: Pair) -> bool {
        a.a == b.a && a.b == b.b
    }
}

fn main() -> !int {
    let p = Pair { a: 1, b: 2 }
    let q = Pair { a: 1, b: 3 }
    print("{Eq.eq(p, p)} {Eq.eq(p, q)}")
    0
}
$ lupin pair.lu
true false

Nothing metaprogrammatic there — an impl and two calls. What a derivation actually contributes is not the four lines; it is the precondition. A generator that writes equality for a type needs that type to be a plain struct, and needs the trait it is implementing to be the one it thinks it is. Those are facts about types, and facts about types are what comptime holds:

trait Eq {
    fn eq(a: Self, b: Self) -> bool
}

struct Pair { a: int, b: int }

impl Eq for Pair {
    fn eq(a: Pair, b: Pair) -> bool {
        a.a == b.a && a.b == b.b
    }
}

comptime fn eq_ready(T: type) -> bool {
    assert(implements(T, Eq))
    assert(typeinfo(T).kind == "struct")
    true
}

const PAIR_IS_EQ_READY: bool = eq_ready(Pair)

fn test_pairs_carry_their_fields() {
    let p = Pair { a: 1, b: 2 }
    assert(p.a == 1 && p.b == 2, "both fields survived construction")
}
$ wolf test ./eq_test.lu
test ./eq_test.lu::test_pairs_carry_their_fields ... ok
wolf test: 1 passed; 0 failed; 0 unsupported; 0 filtered out

implements asks the trait engine a question and gets a bool. Delete the impl block and the answer changes, at compile time, with a location:

trait Eq {
    fn eq(a: Self, b: Self) -> bool
}

struct Point { x: int, y: int }

comptime fn eq_ready(T: type) -> bool {
    assert(implements(T, Eq))
    assert(typeinfo(T).kind == "struct")
    true
}

const POINT_IS_EQ_READY: bool = eq_ready(Point)

fn test_points_carry_their_fields() {
    let p = Point { x: 1, y: 2 }
    assert(p.x == 1 && p.y == 2, "both fields survived construction")
}
error[E0710]: this comptime assertion failed
  --> ./s10.lu:11:5
   |
11 |     assert(implements(T, Eq))
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ evaluated to `false` at compile time
...
16 | const POINT_IS_EQ_READY: bool = eq_ready(Point)
   |                                 --------------- while evaluating `eq_ready`, entered here
   |                                 --------------- while evaluating `POINT_IS_EQ_READY`, entered here
   |
   = note: a failed comptime `assert` stops compilation — it is the witness mechanism for facts the
     checker cannot see on its own.

Two properties of that error are worth holding onto. It is a type error in the ordinary sense — a fact about Point that failed to hold — and it arrives at the line that stated the requirement rather than at the twentieth call site downstream. And it was produced by wolf code you can read: three lines, two asserts, no expansion step, no generated source to debug. Reflection here consumes and produces semantic values only. There is no token stream to inspect, no syntax to rewrite, and therefore no possibility that a derivation quietly means something other than what its inputs say.

That constraint is deliberate and it is the reason there are no macros in v1 (D29). One tier — full evaluation, types as values, reflection — covers derivation, specialization, and constant folding, which is most of what macros are used for, and it covers them without giving any package the ability to rewrite your program’s text. The door is not bricked over: a v2 could add a syntactic tier if this one proves insufficient. It is closed because opening it costs the guarantees §18.4 is about, and nothing so far has needed it enough to pay.

Exercise 18-4 (comprehension · wolf)size_of(Vec2) for a struct of two f64 fields is 16 on every target wolf supports. Predict the verdict of const S = size_of(Vec2) anyway, and then explain why a number that obvious is refused at comptime.

18.3 Where comptime already touched your code

You have been using this tier since chapter 2, under two other names.

The first is every string you have written. "{title:>10} {total:>9.2}" is not a template the runtime parses; the braces and the spec after the colon are read during compilation, and what the program carries is the formatting the spec describes:

fn main() -> !int {
    let title = "regions"
    let total = 3.14159
    print("{title:>10} {total:>9.2}")
    0
}
$ lupin fmt.lu
   regions      3.14

The proof that the spec is compiled rather than interpreted is what happens when you misspell one. A runtime formatter finds out at the moment of printing, on the unlucky code path, in production. This one finds out before there is a program:

fn main() -> !int {
    let total = 3.14159
    print("{total:>zz}")
    0
}
error[E0412]: `z` has no place in a format spec — the grammar is `[[fill]align][+][0][width][.precision][type]` with type one of `b o x X e E f`
 --> ./s12.lu:6:18
  |
6 |     print("{total:>zz}")
  |                  ^^^^ in this format spec
  |

The grammar of a format spec is in the error message, which is where a grammar belongs when a reader has violated it.

Const generics and the N + 1 problem

The second name is const generics. A type may be parameterized by a value, and then two spellings of that value have to be compared for equality — which sounds trivial until you try it:

struct Buf[N: type] {
    len: int,
}

fn shuffle[N: type](b: Buf[N + 1]) -> Buf[1 + N] {
    b
}

fn widen[N: type](b: Buf[N + 2 - 1]) -> Buf[1 + N] {
    b
}

fn closed(b: Buf[2 + 2]) -> Buf[4] {
    b
}

fn test_the_module_compiled() {
    assert(1 + 1 == 2, "three return types agreed with three declarations")
}
$ wolf test ./buf_test.lu
test ./buf_test.lu::test_the_module_compiled ... ok
wolf test: 1 passed; 0 failed; 0 unsupported; 0 filtered out

The module compiles, and the compiling is the claim: three functions, each returning a value of a type it was not literally given. Buf[N + 1] and Buf[1 + N] are the same type. Buf[N + 2 - 1] is that type too. Buf[2 + 2] is Buf[4]. Anyone who has met this in another language knows the alternative: an equality decided by whether two expressions are the same syntax, where N + 1 and 1 + N are different types and the workaround is to pick one spelling and never deviate.

Wolf decides const-expression equality in three steps, and the compiler will recite them. Closed expressions evaluate and compare by value. Arithmetic over generic parameters — + and - — normalizes into a canonical sum, so any rearrangement of the same terms is the same type. Everything past that needs you to say so:

struct Buf[N: type] {
    len: int,
}

fn sneak[N: type](b: Buf[N + N]) -> Buf[N * 2] {
    b
}

fn main() -> !int {
    0
}
error[E0707]: `Buf[N + N]` and `Buf[N * 2]` may be equal, but proving it needs a witness
 --> ./s14.lu:9:5
  |
8 | fn sneak[N: type](b: Buf[N + N]) -> Buf[N * 2] {
  |                                  ------------- the return type is declared here
9 |     b
  |     ^ these const expressions differ beyond linear arithmetic
  |
  = note: const-expression equality is decided in three steps, and the line is fixed: (1) closed
    expressions evaluate and compare by value; (2) `+`/`-` arithmetic over generic
    parameters compares by ring normalization, so `N + 1` equals `1 + N`; (3) anything
    beyond — `*`, `/`, `%`, shifts, bit operators — needs an explicit witness. This pair
    sits at step 3.
  = note: state the equality where the reader can see it: a comptime `assert` on the sizes
    involved, or rewrite both spellings into the same `+`/`-` form.

N + N and N * 2 are equal for every N, and the compiler says so: may be equal. What it declines to do is prove it, and the third step of the rule is where the line sits — multiplication, division, modulo, shifts, and bit operators are off the normalizer’s map. The value of a line drawn there is that it is drawn somewhere, in a note the reader can read, rather than being wherever the last release’s inference happened to reach. A rule you can recite is a rule you can design around; the fix the note names is a witness, which is §18.1’s mechanism doing a third job.

18.4 What it refuses to do

Everything so far has been about what comptime computes. This section is about what it will not touch, and it is the part of the design worth having an opinion about, because the refusals are not an incomplete allowlist waiting to be filled in. They are the feature.

Start with the one every reader has already lived through. You add a dependency to a project. In the ecosystems most of us came from, that act can run code — a build script, an install hook, a post-install step — on your machine, with your credentials, at the moment you type the command. The history of that decision is a list of incidents. Wolf’s version of the decision is that adding a dependency is data entering a manifest, and compiling it evaluates its comptime code inside a box with no filesystem, no network, no environment, no clock, no randomness, and no way to call into native code (D33).

So each of these is refused:

comptime fn embed(path: str) -> str {
    read_text(path)
}
fn main() -> !int {
    const BANNER = embed("banner.txt")
    if BANNER == "" { 1 } else { 0 }
}
comptime fn build_stamp() -> int {
    clock_ms()
}
fn main() -> !int {
    const STAMP = build_stamp()
    if STAMP == 0 { 1 } else { 0 }
}
comptime fn fetch_schema(url: str) -> str {
    net_fetch(url)
}
fn main() -> !int {
    const SCHEMA = fetch_schema("https://example.test/schema.json")
    if SCHEMA == "" { 1 } else { 0 }
}

One error code, three different reasons, and the catalog entry sorts them into exactly two categories:

$ wolf --explain E0701
E0701: comptime code reached for ambient IO

Comptime evaluation is hermetically sandboxed (D33): no filesystem, no
network, no environment variables, no clock, no randomness, no FFI —
the intrinsics available at compile time are an explicit allowlist,
and nothing ambient is on it. Each refusal names its category and its
reason: confinement (compiling a package must never act on or read
the machine that compiles it — `wolf add` must never mean arbitrary
code runs with your credentials) or determinism (the same program and
target must produce bit-identical comptime results on every host).
Compute the value at runtime instead; file contents belong in
*declared build inputs* through the package manifest, never in an
evaluator capability.

Confinement covers the file read and the network fetch: a build must not act on the machine it runs on, and it must not go looking for one. Determinism covers the clock and the randomness: two builds of the same source for the same target must produce the same answer, and a timestamp is the shortest way to break that. Reading an environment variable falls under both at once — it reads the machine, and it varies between machines — and the catalog files it under confinement.

Each refusal buys something specific, and the three are worth separating because they are usually collapsed into one hand wave about safety.

Caching gets a key it can trust. If comptime cannot read anything outside the program, then the program is the input, and a compiler that has evaluated this code once can reuse the answer with no risk that the answer secretly depended on a file that has since changed. An evaluator with ambient reads has no honest cache key, so it either re-runs everything or serves you a stale artifact and hopes.

Reproducibility becomes checkable rather than aspirational. Two machines compiling the same source for the same target reach the same constants, and the reason is not discipline — it is that the axes along which they differ are not reachable.

Auditing becomes reading. If comptime could open a socket, auditing a dependency would mean proving that no comptime expression anywhere in it ever does; the job would be unbounded. Because it cannot, a reviewer’s question shrinks to what the manifest declares, which is a document a person can read in an afternoon.

The meters

The sandbox has one more wall, and it is the one you will meet by accident. Comptime code runs on the compiler’s clock, so a comptime loop that does not finish is a build that does not finish. Wolf’s answer is that evaluation is metered: a step count, a heap, and a call depth, each with a default and each reporting itself by name when it runs out.

Burn the steps and the compiler tells you which meter emptied:

comptime fn spin() -> int {
    while true {}
    0
}
fn main() -> !int {
    const N = spin()
    if N == 0 { 0 } else { 1 }
}
error[E0702]: comptime evaluation ran out of fuel after 1000000 steps
 --> ./s18.lu:9:15
  |
9 |     const N = spin()
  |               ^^^^^^ evaluation stopped here
  |               ------ while evaluating `spin`, entered here
  |               ------ while evaluating `main`, entered here
  |
  = note: fuel bounds how long the compiler will evaluate before concluding the computation is
    runaway — a build can be slow, never hung (D33).
help: raise the budget here: `#[budget(fuel = 2000000)]`
  |
9 |     #[budget(fuel = 2000000)]
  |

Recurse instead of looping and a different meter answers, because the resource you exhausted is a different resource:

comptime fn shelves(n: int) -> int {
    if n == 0 { 0 } else { 1 + shelves(n - 1) }
}
fn main() -> !int {
    const N = shelves(300)
    if N == 300 { 0 } else { 1 }
}
error[E0704]: comptime evaluation recursed past 256 call frames
 --> ./s19.lu:5:32
  |
5 |     if n == 0 { 0 } else { 1 + shelves(n - 1) }
  |                                ^^^^^^^^^^^^^^ the call that went over the limit
  |                                -------------- while evaluating `shelves` — 254 recursive frames
...
8 |     const N = shelves(300)
  |               ------------ while evaluating `shelves`, entered here
  |               ------------ while evaluating `main`, entered here
  |
  = note: call depth is a resource limit, not a host stack: deep recursion is refused with this
    report instead of crashing the compiler (D33).
help: raise the budget here: `#[budget(depth = 512)]`
  |
8 |     #[budget(depth = 512)]
  |

Both errors end the same way: with the attribute that raises the limit, spelled out at the site that needs it. That is the design’s position on the tension every metered evaluator has — the compiler cannot tell a runaway from a computation that is merely large, and you can, so the decision is yours to state and the default is what protects you until you state it. A build may be slow. It may not hang.

What the attribute cannot do is turn a meter off. Try, with a body so trivial that the limit is obviously unnecessary, and the rejection is about the attribute rather than the workload:

comptime fn ten() -> int {
    10
}
fn main() -> !int {
    #[budget(fuel = 0)]
    const N = ten()
    if N == 10 { 0 } else { 1 }
}
error[E0709]: a comptime budget cannot be turned off — `fuel = 0` would disable the limit
 --> ./s20.lu:8:14
  |
8 |     #[budget(fuel = 0)]
  |              ^^^^^^^^ budgets are raised, never removed
  |
  = note: the sandbox guarantee (D33) includes bounded evaluation: every budget has a default, a
    per-site override, and a hard ceiling — there is no spelling that removes one.

Budgets are raised, never removed. There is no spelling that disables one, which means there is no dependency that can disable one either — and a metered evaluator with an off switch is an unmetered evaluator with an extra step.

What is not in the box

Three absences, stated plainly, because each of them is something a reader arriving from another language will look for.

Comptime cannot modify a declaration other than the one it computes. It produces values and types; it does not reach into your struct and add a field, and it does not attach itself to somebody else’s function. What you see in a file is what the file says.

Comptime cannot observe syntax. typeinfo and implements answer semantic questions — fields, kinds, whether a trait is implemented — and there is no way to ask for the tokens of anything. A metaprogram that cannot see spelling cannot depend on spelling, which is why the N + 1 normalization of §18.3 was a decision the language got to make rather than a compatibility hazard.

And comptime cannot be nondeterministic, which is the sandbox restated as a property rather than a list. Every intrinsic on the allowlist is a function of its arguments. This is the invariant the other two absences protect, and it is the one the whole design leans on.

Exercise 18-6 (spelunking · wolf) — Run wolf --explain E0701 and read the entry in full. It names two distinct reasons a comptime capability can be refused. Name both, and sort these refusals under them: a clock read, a network fetch, an environment variable.

Exercise 18-7 (comprehension · wolf) — Five expression tiles. Sort each onto the comptime side of the boundary or the runtime side before running anything: 6 * 7; a function from a type to a type; a file read; a clock read; a network fetch. Then check the three you sorted as refused, with three one-line programs. Do the three diagnostics give the same reason?

Exercise 18-8 (comprehension · wolf) — A reader decides budgets are noise and writes #[budget(fuel = 0)] to turn the meter off. Predict what the compiler does with a trivial call under that attribute — a comptime fn that returns 10 and computes nothing.

Exercise 18-9 (comprehension · wolf) — Two runaway programs, two different budgets. Before running, match each to the resource it exhausts and the E-code it earns:

// program A
comptime fn dive(n: int) -> int {
    dive(n + 1)
}
// program B
comptime fn spin() -> int {
    while true {}
    0
}

Exercise 18-10 (extension (break-it-on-purpose) · wolf) — Earn E0703 — the heap budget — using only a while loop and a var, without tripping fuel first. (You will need to grant fuel to get there.)

Exercise 18-12 (design) — The sandbox refuses a file read (E0701) but the catalog entry points at declared build inputs through the package manifest instead. Draw the line between the two designs: what exactly does declaring an input buy that an ambient read does not have? Name the failure the ambient read permits in each of: caching, cross-machine reproducibility, and auditing a dependency you did not write.

Where this leaves you

Four spellings, one mechanism. comptime fn moves a computation to the compiler’s clock. const is where its answer lands. assert inside a comptime function is how a fact about your program gets a location. And typeinfo, implements, and a parameter of type type are how the subject of that fact can be a type instead of a number.

The refusals are the same list read from the other side: no ambient inputs, no unbounded evaluation, no reaching into declarations, no looking at syntax. Every one of them is why a package’s comptime code is something you can compile without first deciding whether you trust its author, and that sentence is the whole reason the tier is shaped like this.