Structs in D
A struct in D is a value type declared with the struct keyword, and unlike a class it has no inheritance, no virtual function table, and no implicit heap allocation. A local struct variable lives on the stack (or wherever its containing scope lives), and every struct type automatically gets a compiler-generated .init value used to default-initialize instances, so struct Point { int x; int y; } Point p; gives you a Point with x and y both zero without writing any constructor. Structs can still have member functions, static members, constructors, a destructor, and operator overloads, which makes them just as capable as classes for encapsulating behavior — the key difference is ownership and copying semantics, not feature richness. Because structs carry no hidden reference-counting or garbage-collector bookkeeping by default, they are the natural choice in D for small, self-contained data like coordinates, colors, durations, or resource handles that should behave like plain values.
Cricket analogy: A struct is like a single scorecard entry for one batsman — runs, balls faced, fours, sixes — a compact, self-contained value with no inheritance from some abstract 'Player' hierarchy, just plain fields describing one innings.
Construction and Methods
Structs can define one or more constructors using this(...), and D supports constructor overloading by parameter list, so struct Point { int x, y; this(int x, int y) { this.x = x; this.y = y; } } lets you write Point(3, 4). Member functions are declared like ordinary functions inside the struct body and are called with dot syntax, p.distanceTo(other), and Uniform Function Call Syntax (UFCS) means a free function taking a Point as its first argument can also be called as if it were a method, so distanceTo(p, other) and p.distanceTo(other) are interchangeable. static members belong to the type itself rather than any instance — useful for named-constructor patterns like static Point origin() { return Point(0, 0); } — and structs can also declare alias this to a member, which lets the struct implicitly convert to that member's type wherever a conversion is needed, a lightweight substitute for inheritance-based subtyping.
Cricket analogy: A struct constructor is like filling out a scorecard entry the moment a batsman walks in — Player('Kohli', battingPosition: 4) — while a static named constructor like Innings.declared() is like a captain's declaration entry created without needing a live delivery.
Value Semantics and Copying
Because structs are value types, assigning one struct variable to another, Point b = a;, copies every field of a into b, and the two variables are thereafter completely independent — mutating b.x never affects a.x. This also applies to function parameters: passing a struct by value copies it onto the callee's stack, so void move(Point p) { p.x += 1; } has no effect on the caller's original struct, whereas void move(ref Point p) { p.x += 1; } mutates the caller's variable directly because ref passes by reference. When a struct's fields include a pointer or a dynamically allocated resource, a naive field-by-field copy can be dangerous — two struct instances would end up pointing at the same resource — so D lets you define a postblit constructor, this(this) { ... }, which runs automatically after every implicit copy and gives you a chance to deep-copy or reference-count whatever the pointer refers to.
Cricket analogy: Copying a struct by value is like photocopying a physical scorecard for a second scorer — crossing out a run on the copy never changes the original scorer's sheet, since ref would be needed to hand over the actual original card.
Comparison, Immutability, and Object Features
By default, D auto-generates a field-by-field opEquals for structs, so a == b compares every member, and a similarly generated toHash lets structs work as associative array keys out of the box; you can override opEquals, opCmp, or toString when custom comparison or formatting logic is needed. Structs interact cleanly with D's const and immutable qualifiers — an immutable Point is guaranteed never to change after construction, which the compiler enforces, and functions can accept in Point p (implicitly const) to promise they will not mutate the caller's struct. Structs can also declare an invariant() block that the compiler checks after every public method call in debug builds, asserting that the struct's fields remain in a valid state, which is a lightweight way to enforce class-like invariants without giving up value semantics.
Cricket analogy: Auto-generated opEquals on a struct is like an umpire's review comparing two recorded deliveries field by field — same bowler, same speed, same outcome — to decide if they're an identical replay rather than checking just one attribute.
import std.stdio;
import std.math : sqrt;
struct Point
{
int x, y;
this(int x, int y)
{
this.x = x;
this.y = y;
}
static Point origin()
{
return Point(0, 0);
}
double distanceTo(Point other) const
{
immutable dx = x - other.x;
immutable dy = y - other.y;
return sqrt(cast(double)(dx * dx + dy * dy));
}
string toString() const
{
import std.format : format;
return format("(%d, %d)", x, y);
}
}
void nudge(ref Point p)
{
p.x += 1; // mutates the caller's Point because it's passed by ref
}
void main()
{
auto a = Point(3, 4);
auto b = a; // value copy: independent from a
b.x = 99;
writeln(a, " ", b); // (3, 4) (99, 4)
writeln(a.distanceTo(Point.origin())); // 5
nudge(a);
writeln(a); // (4, 4)
}Structs allocated as local variables carry no garbage-collector or vtable overhead: their memory is reclaimed automatically when the enclosing scope ends, making them the idiomatic choice in D for small, frequently-created values like coordinates or ranges.
If a struct holds a raw pointer or a manually allocated resource, the compiler's default field-by-field copy will duplicate the pointer, not the pointee — two struct instances will then alias the same resource. Define a postblit (this(this)) or disable copying with @disable this(this); to avoid double-free or aliasing bugs.
- Structs are value types with no inheritance and no vtable; a local struct's default .init zero-initializes its fields.
- Constructors use this(...) and can be overloaded; static members and factory functions provide named-constructor patterns.
- UFCS lets free functions taking a struct as the first parameter be called with method syntax.
- Assignment and by-value parameter passing copy every field; use ref to mutate the caller's original struct.
- A postblit constructor (this(this)) customizes what happens on every implicit copy, essential when a struct owns a pointer or resource.
- The compiler auto-generates opEquals and toHash; override opCmp or toString for custom comparison and formatting.
- invariant() blocks and const/immutable parameters let structs enforce validity and read-only guarantees without giving up value semantics.
Practice what you learned
1. What is the default memory location for a local struct variable in D?
2. What does passing a struct by ref to a function achieve that passing by value does not?
3. Why is a postblit constructor (this(this)) important for a struct that holds a raw pointer to allocated memory?
4. What does the compiler auto-generate for every struct by default?
5. What is UFCS (Uniform Function Call Syntax) as it applies to structs?
Was this page helpful?
You May Also Like
Classes in D
How D's reference-type classes work: single inheritance, polymorphism, the Object root, and construction/destruction lifecycle.
Arrays and Slices in D
How D's static arrays, dynamic arrays, and slices differ in memory ownership, mutation, and performance, and how to use them correctly.
Manual Memory Management in D
How to opt out of D's garbage collector using malloc/free, std.experimental.allocator, and struct-based RAII for deterministic, low-latency memory control.
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