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.
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).
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
1. What triggers a D function to be executed via CTFE rather than at runtime?
2. What does the __ctfe boolean allow a function to do?
3. Which of the following is NOT something CTFE can typically do, even in modern D compilers?
4. Why might overusing enum for large computed tables be a problem?
5. What is the primary purpose of pairing CTFE with static assert?
Was this page helpful?
You May Also Like
Mixins and Metaprogramming
See how D's string mixins and template mixins let you generate and reuse code at compile time, powered by CTFE and compile-time reflection via __traits.
Ranges in D
Learn how D's range interface unifies iteration over arrays, containers, and lazy sequences, and how to compose algorithms with std.range and std.algorithm.
Error Handling in D
Learn D's split between recoverable Exceptions and unrecoverable Errors, the scope(exit)/failure/success guards, and std.exception helpers like enforce and nothrow.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics