D Code Style Guide
Follow the official DStyle (local copy: dstyle.md). Key points:
- Whitespace: 4-space indentation, one statement per line
- Braces: Own line for functions and type definitions
- Naming:
camelCasefor constants/enum members/variables/functions/UDAs,PascalCasefor types,snake_casefor modules, if a name would conflict with a keyword append_, e.g.,class_, all letters in acronyms should have the same case, e.g.,xmlLoad,parseXML
Module Layout
Organize D modules in this order:
- Module-level Ddoc - Documentation for the entire module
- Module declaration -
module sparkles.core_cli.example; - Imports - Grouped as described below
- Ddoc-ed module-level unit tests - Usage examples for the module as a whole
- Public API - Most important user-facing items first (public aliases, types, functions)
- Implementation details - Private aliases, types, functions
- Non-Ddoc module-level unit tests - Integration tests using multiple module members
Unit tests for a specific function or type should follow that declaration directly.
See DDoc for documentation comment syntax and conventions.
Imports
Group imports in this order, separated by a single empty line between groups:
core.*modules (DRuntime)std.*modules (Phobos)- External dependencies
- Modules from other sub-packages of this project
- Modules from the same sub-package
import core.memory : pureMalloc, pureFree;
import std.range.primitives : put, empty, front, popFront;
import std.traits : isSomeChar, isSomeString, isNumeric;
import sparkles.core_cli.term_style : Style, stylize;Import Best Practices
- Always use selective imports - Import only the symbols you need, not entire modules
- Prefer local (scoped) imports - Use function-level or type-level imports for clarity, similar to how variables should have the smallest possible scope. Bonus: templates with scoped imports that are never instantiated won't trigger the import
- Use renamed imports to avoid name clashes or improve clarity:
import std.file : writeFile = write; // Avoid clash with std.stdio.writeEponymous Templates
Use short eponymous template syntax:
// Good
enum isSpecial(T) = is(T == int) || is(T == long);
// Avoid
template isSpecial(T)
{
enum isSpecial = is(T == int) || is(T == long);
}Expression-Based Contracts (DIP1009)
// Good
int divide(int a, int b)
in (b != 0)
out (r; r * b == a)
{
return a / b;
}
// Avoid
int divide(int a, int b)
in
{
assert(b != 0);
}
out (r)
{
assert(r * b == a);
}
do
{
return a / b;
}Expression-Based Functions (DIP1043)
For simple functions, use => syntax:
// Good
int square(int x) => x * x;
// Avoid
int square(int x)
{
return x * x;
}Static Foreach
Use static foreach over tuples and AliasSeq:
// Good
static foreach (T; AliasSeq!(int, long, float))
{
pragma(msg, T.stringof);
}
// Avoid
foreach (T; AliasSeq!(int, long, float))
{
pragma(msg, T.stringof);
}Copy Constructors (DIP1018)
Use copy constructors instead of postblit:
struct S
{
int* ptr;
// Good
this(ref return scope const S another)
{
ptr = new int(*another.ptr);
}
// Avoid
// this(this) { ptr = new int(*ptr); }
}Input Parameters
Use in for read-only parameters (implies const scope):
// Good
void process(in Config config) { ... }
// Avoid
void process(const ref Config config) { ... }Note: in may be omitted for primitive types and immutable(T)[] slices (e.g., string).
Named Arguments (DIP1030)
Use named arguments for clarity at call sites:
auto result = createWidget(
width: 100,
height: 200,
visible: true,
resizable: false,
);Interpolated Expression Sequences (DIP1036)
Use IES (i"...") when interspersing string literals with expressions. Preference order:
- IES — Type-safe, enables context-aware encoding
std.format— When format specifiers are needed (%08x,%.2f)- Manual concatenation — Avoid
import std.conv : text;
import std.stdio : writeln;
string name = "Alice";
int count = 42;
// Good: IES with writeln (no allocation)
writeln(i"Hello, $(name)! Count: $(count)");
// Good: IES converted to string
string msg = i"Hello, $(name)! Count: $(count)".text;
// Good: std.format when format specifiers needed
import std.format : format;
string hex = format!"Value: 0x%08X"(count);
// Avoid: manual concatenation
string bad = "Hello, " ~ name ~ "! Count: " ~ count.to!string;Key rules:
- IES produces a tuple, not a string — use
.textor pass to IES-accepting functions - Prefer
writeln(i"...")overwriteln(i"...".text)to avoid allocation - For security-sensitive contexts (SQL, HTML, URLs), use dedicated IES-processing functions that escape interpolated values
See Interpolated Expression Sequences for complete patterns including safe SQL queries, HTML templates, and structured logging.