100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Compile-Time Function Execution (CTFE)

Understand how D executes ordinary functions at compile time to compute constants, validate data, and generate code before the program ever runs.

Advanced DAdvanced10 min readJul 10, 2026
Analogies

What Is CTFE?

D doesn't require a separate templating language for compile-time computation — CTFE (Compile-Time Function Execution) allows the compiler to interpret ordinary D function bodies during compilation whenever every argument is known at compile time and the result is used in a compile-time context, such as initializing an enum or sizing a static array. Unlike C++'s constexpr, a D function needs no special annotation to be CTFE-eligible; the same function can run at compile time in one call site and at runtime in another, and the compiler decides based on context, e.g. enum table = buildLookupTable(); forces compile-time evaluation while auto table = buildLookupTable(); at runtime would execute identically but during program execution.

🏏

Cricket analogy: It's like a bowling machine that can be operated by a human coach live in the nets (runtime) or pre-programmed to fire an identical sequence of deliveries during ball-tracking calibration before a match (compile time) — same machine, same settings, different moment of execution.

Rules and Limitations of CTFE

Not every D construct is CTFE-safe: the interpreter that runs during compilation cannot perform raw pointer arithmetic to unknown memory, cannot call into external C functions or inline assembly, and historically could not allocate class instances or dynamic arrays before DMD gained progressively better support for these (modern D compilers support GC-backed allocation, associative arrays, and even some exception handling in CTFE). If a function violates these constraints and it's invoked in a context that demands compile-time evaluation (like an enum initializer), the compiler emits an error at the call site rather than silently falling back to runtime, which is why library authors often mark boundary conditions explicitly with static assert.

🏏

Cricket analogy: It's like the pre-match pitch inspection by curators — they can assess soil moisture and grass length (safe compile-time checks) but cannot predict a freak weather event mid-game (like calling live external systems), so certain judgments must wait until the actual toss and play.

d
int factorial(int n)
{
    return n <= 1 ? 1 : n * factorial(n - 1);
}

// Forces compile-time evaluation; result is baked into the binary.
enum int[8] FACTORIALS = () {
    int[8] table;
    foreach (i; 0 .. 8)
        table[i] = factorial(i);
    return table;
}();

static assert(FACTORIALS[5] == 120);

Since D 2.070+, CTFE supports a wide subset of the language including associative arrays, exceptions, and dynamic array allocation — but it still cannot call extern(C) functions, use inline asm, or read files, since the compiler has no access to the target machine's runtime environment during compilation.

Practical Uses: enum, static assert, and __ctfe

The most common CTFE trigger is an enum declaration, which forces its initializer to be evaluated at compile time and treats the result as a manifest constant with no storage — this is how libraries like std.regex's ctRegex or std.uni build lookup tables baked directly into the compiled binary with zero runtime cost. Inside a function, the special __ctfe boolean lets code detect whether it's currently executing at compile time versus runtime and branch accordingly (e.g., using a simpler algorithm during CTFE that avoids operations the interpreter can't handle), while static assert(condition, message) combines CTFE with compile-time validation to reject invalid template instantiations or configuration values before the program can even be built.

🏏

Cricket analogy: It's like a broadcaster pre-recording standard player stat graphics (like a batting average card) before the match so they can be flashed up instantly (enum), while still having a live commentator ready to describe an unusual, unscripted moment as it actually happens (the runtime branch).

d
string describe(int level)
{
    if (__ctfe)
        return "compile-time: level " ~ toStringCT(level); // simplified path
    else
        return format("runtime: level %d", level); // std.format, not CTFE-safe pre-2.070
}

Overusing enum for large computed tables can bloat compile times and binary size dramatically — a CTFE loop that builds a 100,000-entry table re-runs on every compilation and every module that imports it, so prefer immutable static this() runtime initialization for large data sets and reserve CTFE for genuinely small, reusable constants.

  • CTFE lets ordinary D functions execute during compilation with no special constexpr-like annotation required.
  • A function becomes CTFE-eligible automatically when all its arguments are compile-time constants and its result is used in a compile-time context like enum.
  • Modern D CTFE supports much of the language, including associative arrays and exceptions, but still forbids raw pointer tricks, extern(C) calls, and inline assembly.
  • enum forces compile-time evaluation and produces a manifest constant with zero runtime storage cost.
  • __ctfe is a compiler-provided boolean that lets a function branch between a CTFE-safe path and a normal runtime path.
  • static assert combines CTFE with compile-time validation, failing the build if a condition isn't met.
  • Large CTFE-computed tables can slow compilation significantly, so CTFE is best reserved for small, genuinely reusable constants.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#DProgrammingStudyNotes#CompileTimeFunctionExecutionCTFE#Compile#Time#Function#Execution#Functions#StudyNotes#SkillVeris