Compile-Time staticIf in Nim: Remove Unused Branches Before Code Generation
Use a Nim macro with untyped parameters to build a compile-time static-if that drops unused branches from the AST.
23 Nov 2025, 15:14 UTC

The problem: runtime branches that never execute
A normal Nim if chooses between branches while the program runs. Even when a condition is a constant such as true, the compiler may still type-check both sides and emit symbols for the discarded path. That costs binary size and can prevent inlining or dead-code elimination.
The useful takeaway: define a macro that builds a when expression during compilation. Because when is resolved before type checking, the branch that does not match the constant condition never enters the generated code.
How a compile-time static-if works
A Nim macro receives raw syntax trees, not evaluated values. Parameters marked untyped arrive unevaluated, so the macro can rearrange them without triggering premature type errors. The quote do: block constructs new AST nodes, and the compiler splices the result into the call site.
Terms: AST is the abstract syntax tree, the compiler's in-memory model of your code. untyped tells Nim to pass an expression as raw syntax instead of evaluating it first. quote builds AST from ordinary Nim syntax.
Worked example: a static branch selector
Create a file named static_if_example.nim:
macro staticIf(cond: untyped, thenBranch: untyped, elseBranch: untyped): untyped =
quote do:
when cond:
thenBranch
else:
elseBranch
const debugEnabled = true
proc initialize() =
staticIf(debugEnabled,
echo "debug logging started",
echo "running in release mode")
initialize()
Run this from a terminal in your project directory. No special permissions are required:
nim c -r static_if_example.nim
Expected check: compilation succeeds and the program prints only the branch matching debugEnabled. With true, you should see the debug message and not the release message. Change the constant to false, recompile, and the output should switch to the other branch. This demonstrates that the choice happened at compile time, not while the program was running.
Risk: if you pass a runtime variable instead of a compile-time constant, the compiler rejects the when condition. Macro failures appear as compile errors pointing at generated code, which can be harder to read than ordinary type errors.
Inspecting the expansion
To verify that the unused branch is gone, ask the compiler to show intermediate code:
nim c -d:release --verbosity:2 static_if_example.nim
Look for the expanded when block in the printed Nim code. The branch that does not match the constant should be absent. Nim's nimpp filter can also display macro-expanded output, though exact flags depend on your installation.
Limits and common mistakes
- No runtime data. A macro cannot read a variable's current value; it sees only the compile-time representation. Passing a runtime flag produces an error rather than a dynamic branch.
- Missing
untyped. Without it, Nim evaluates macro arguments before the macro runs. If one branch contains code that would not type-check in every context, you get an early type error. - No type information during expansion. The macro operates before type checking, so it cannot inspect the type of
thenBranchorelseBranchto make decisions. - Quote syntax errors. Mismatched brackets or invalid statements inside
quote do:produce syntax errors at expansion time, not at the call site. - Identifier capture. Generated names can accidentally refer to variables in the surrounding scope. For generated identifiers, use
gensymto create unique names.
When to use it and when to avoid it
Use this pattern for configuration flags, feature toggles, or platform-specific code where the decision is known at compile time and you want the discarded branch completely absent from the binary. Avoid it for ordinary business logic that changes while the program runs; a regular if is clearer and easier to debug. Macro-generated code also increases compile time and can reduce readability, so keep the macro small and the call sites obvious.
Practical verification
- Compile
static_if_example.nimwithnim c -rand confirm only the expected branch prints. - Flip the compile-time constant and recompile; confirm the output switches to the alternate branch.
- Run with
--verbosity:2and check that the unused branch does not appear in the intermediate code.
If the branch remains in the expanded output or the compiler rejects your condition, the value is not a compile-time constant or the macro arguments are not untyped.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.