Skip to content
Mixins and Metaprogramming
step 1/4

Reading — step 1 of 4

Learn

~2 min readConcurrency, Mixins, C Interop

D's metaprogramming is more accessible than C++ templates. Three forms: template mixins, string mixins, and CTFE.

Template mixins

Inject a template's contents into the current scope:

mixin template Logging() {
    void log(string msg) {
        writeln("[", typeof(this).stringof, "] ", msg);
    }
}

class Service {
    mixin Logging;       // injects log() method
}

auto s = new Service;
s.log("started");        // "[Service] started"

Useful for cross-cutting concerns: logging, equals/hash, accessors.

String mixins

Inject literal source code generated at compile time:

int x = mixin("3 + 4");      // x = 7

// Generated accessor:
string genAccessor(string name, string type) {
    return type ~ " _" ~ name ~ ";\n" ~
           type ~ " " ~ name ~ "() { return _" ~ name ~ "; }\n" ~
           "void " ~ name ~ "(" ~ type ~ " v) { _" ~ name ~ " = v; }";
}

struct Person {
    mixin(genAccessor("name", "string"));
    mixin(genAccessor("age", "int"));
}

CTFE generates the source string; mixin compiles it. Powerful but harder to read.

CTFE — Compile-Time Function Execution

Any pure function with compile-time-known inputs runs at compile time:

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

enum FACT_10 = factorial(10);    // computed during compile
static immutable lookup = factorial(20);   // also CTFE

__traits and reflection

D has compile-time reflection — query types and members:

import std.traits;

struct Person {
    string name;
    int age;
}

static foreach (member; __traits(allMembers, Person)) {
    writeln(member);    // "name", "age"
}

static if (hasMember!(Person, "name")) {
    // ...
}

std.traits has dozens of helpers: isArray!T, isCallable!T, Parameters!fn, ReturnType!fn.

Auto-serializer example

Combine the above for runtime-free JSON serialization:

import std.format, std.traits;

string toJson(T)(T obj) {
    string result = "{";
    bool first = true;
    static foreach (member; __traits(allMembers, T)) {
        if (!first) result ~= ",";
        result ~= format(`"%s":"%s"`, member, __traits(getMember, obj, member));
        first = false;
    }
    return result ~ "}";
}

struct User { string name; int age; }
auto u = User("Ada", 36);
writeln(toJson(u));   // {"name":"Ada","age":"36"}

Similar to what frameworks do — except entirely at compile time.

When to reach for metaprogramming

  • DSLs (regex compilers, parser generators)
  • Cross-cutting concerns (logging, validation)
  • Generic libraries (serializers, ORMs)
  • Performance — moving work from runtime to compile time

D's metaprogramming is part of the language, not a separate macro system. That's a big quality-of-life improvement over C/C++.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…