- Struct Layout
- Plain Old Data
- Opaque Structs and Unions
- Static Initialization of Structs
- Static Initialization of Unions
- Dynamic Initialization of Structs
- Struct Literals
- Struct Properties
- Struct Instance Properties
- Struct Field Properties
- Const, Immutable and Shared Structs
- Struct Constructors
- Struct Postblits
- Struct Destructors
- Struct Invariants
- Identity Assignment Overload
- Nested Structs
- Unions and Special Member Functions
Structs, Unions
Whereas classes are reference types, structs are value types. Structs and unions are simple aggregations of data and their associated operations on that data.
AggregateDeclaration: ClassDeclaration InterfaceDeclaration StructDeclaration UnionDeclaration StructDeclaration: struct Identifier ; struct Identifier AggregateBody StructTemplateDeclaration AnonStructDeclaration AnonStructDeclaration: struct AggregateBody UnionDeclaration: union Identifier ; union Identifier AggregateBody UnionTemplateDeclaration AnonUnionDeclaration AnonUnionDeclaration: union AggregateBody AggregateBody: { DeclDefsopt }
A struct is defined to not have an identity; that is, the implementation is free to make bit copies of the struct as convenient.
Structs and unions may not contain an instance of themselves, however, they may contain a pointer to the same type.
- Bit fields are supported with the bitfields template.
Struct Layout
The non-static data members of a struct are called fields. Fields are laid out in lexical order. Fields are aligned according to the Align Attribute in effect. Unnamed padding is inserted between fields to align fields. There is no padding between the first field and the start of the object.
Non-static function-nested D structs, which access the context of their enclosing scope, have an extra field.
- The default layout of the fields of a struct is an exact match with the associated C compiler.
- The padding data can be accessed, but its contents are undefined.
- When laying out a struct to match an externally defined layout, use align attributes to describe an exact match. Using a Static Assert to ensure the result is as expected.
- Although the contents of the padding are often zero, do not rely on that.
Plain Old Data
A struct or union is Plain Old Data (POD) if it meets the following criteria:
- it is not nested
- it has no postblits, destructors, or assignment operators
- it has no ref fields or fields that are themselves non-POD
Opaque Structs and Unions
Opaque struct and union declarations do not have a AggregateBody:
struct S; union U;
The members are completely hidden to the user, and so the only operations on those types are ones that do not require any knowledge of the contents of those types. For example:
struct S; S.sizeof; // error, size is not known S s; // error, cannot initialize unknown contents S* p; // ok, knowledge of members is not necessary
They can be used to implement the PIMPL idiom.
Static Initialization of Structs
Static struct members are by default initialized to whatever the default initializer for the member is, and if none is supplied, to the default initializer for the member's type.
struct S { int a = 4; int b; } static S x; // a is set to 4, b to 0
If a static initializer is supplied, the members are initialized by the memberName:expression syntax. The members may be initialized in any order. Initializers for statics must be evaluatable at compile time. Members not specified in the initializer list are default initialized.
struct S { int a, b, c, d = 7; } static S x = { a:1, b:2 }; // c is set to 0, d to 7 static S z = { c:4, b:5, a:2, d:5 }; // z.a = 2, z.b = 5, z.c = 4, z.d = 5
C-style initialization, based on the order of the members in the struct declaration, is also supported:
static S q = { 1, 2 }; // q.a = 1, q.b = 2, q.c = 0, q.d = 7
The two styles can be combined:
static S q = { 1, d:3 }; // q.a = 1, q.b = 0, q.c = 0, q.d = 3
Struct literals can also be used to initialize statics, but they must be evaluable at compile time.
static S q = S( 1, 2+3 ); // q.a = 1, q.b = 5, q.c = 0, q.d = 7
Static Initialization of Unions
Unions are initialized explicitly.
union U { int a; double b; } static U u = { b : 5.0 }; // u.b = 5.0
Other members of the union that overlay the initializer, but occupy more storage, have the extra storage initialized to zero.
Dynamic Initialization of Structs
The static initializer syntax can also be used to initialize non-static variables. The initializer need not be evaluable at compile time.
struct S { int a, b, c, d = 7; } void test(int i) { S q = { 1, b:i }; // q.a = 1, q.b = i, q.c = 0, q.d = 7 }
Structs can be dynamically initialized from another value of the same type:
struct S { int a; } S t; // default initialized t.a = 3; S s = t; // s.a is set to 3
If opCall is overridden for the struct, and the struct is initialized with a value that is of a different type, then the opCall operator is called:
struct S { int a; static S opCall(int v) { S s; s.a = v; return s; } static S opCall(S v) { assert(0); } } S s = 3; // sets s.a to 3 using S.opCall(int) S t = s; // sets t.a to 3, S.opCall(S) is not called
Struct Literals
Struct literals consist of the name of the struct followed by a parenthesized argument list:
struct S { int x; float y; } int foo(S s) { return s.x; } foo( S(1, 2) ); // set field x to 1, field y to 2
Struct literals are syntactically like function calls. If a struct has a member function named opCall, then struct literals for that struct are not possible. See also opCall operator overloading for the issue workaround. It is an error if there are more arguments than fields of the struct. If there are fewer arguments than fields, the remaining fields are initialized with their respective default initializers. If there are anonymous unions in the struct, only the first member of the anonymous union can be initialized with a struct literal, and all subsequent non-overlapping fields are default initialized.
Struct Properties
Name | Description |
---|---|
.sizeof | Size in bytes of struct |
.alignof | Size boundary struct needs to be aligned on |
Struct Instance Properties
Name | Description |
---|---|
.tupleof | An expression sequence of all struct fields - see Class Properties for a class-based example. |
Struct Field Properties
Name | Description |
---|---|
.offsetof | Offset in bytes of field from beginning of struct |
Const, Immutable and Shared Structs
A struct declaration can have a storage class of const, immutable or shared. It has an equivalent effect as declaring each member of the struct as const, immutable or shared.
const struct S { int a; int b = 2; } void main() { S s = S(3); // initializes s.a to 3 S t; // initializes t.a to 0 t = s; // error, t.a and t.b are const, so cannot modify them. t.a = 4; // error, t.a is const }
Struct Constructors
Struct constructors are used to initialize an instance of a struct. The ParameterList may not be empty. Struct instances that are not instantiated with a constructor are default initialized to their .init value.
struct S { int x, y; this() // error, cannot implement default ctor for structs { } this(int a, int b) { x = a; y = b; } } void main() { S a = S(4, 5); auto b = S(); // same as auto b = S.init; }
A constructor qualifier allows the object to be constructed with that specific qualifier.
struct S1 { int[] a; this(int n) { a = new int[](n); } } struct S2 { int[] a; this(int n) immutable { a = new int[](n); } } void main() { // Mutable constructor creates mutable object. S1 m1 = S1(1); // Constructed mutable object is implicitly convertible to const. const S1 c1 = S1(1); // Constructed mutable object is not implicitly convertible to immutable. // immutable i1 = S1(1); // Mutable constructor cannot construct immutable object. // auto x1 = immutable S1(1); // Immutable constructor cannot construct mutable object. // auto x2 = S2(1); // Constructed immutable object is not implicitly convertible to mutable. // S2 m2 = immutable S2(1); // Constructed immutable object is implicitly convertible to const. const S2 c2 = immutable S2(1); // Immutable constructor creates immutable object. immutable i2 = immutable S2(1); }
If struct constructor is annotated with @disable and has empty parameter, the struct is disabled construction without calling other constructor.
struct S { int x; // Disables default construction, function body can be empty. @disable this(); this(int v) { x = v; } } void main() { //S s; // default construction is disabled //S s = S(); // also disabled S s = S(1); // construction with calling constructor }
Struct Postblits
Postblit: this ( this ) MemberFunctionAttributesopt ; this ( this ) MemberFunctionAttributesopt FunctionBody
Copy construction is defined as initializing a struct instance from another struct of the same type. Copy construction is divided into two parts:
- blitting the fields, i.e. copying the bits
- running postblit on the result
The first part is done automatically by the language, the second part is done if a postblit function is defined for the struct. The postblit has access only to the destination struct object, not the source. Its job is to ‘fix up’ the destination as necessary, such as making copies of referenced data, incrementing reference counts, etc. For example:
struct S { int[] a; // array is privately owned by this instance this(this) { a = a.dup; } }
Disabling struct postblit makes the object not copyable.
struct T { @disable this(this); // disabling makes T not copyable } struct S { T t; // uncopyable member makes S also not copyable } void main() { S s; S t = s; // error, S is not copyable }
Unions may not have fields that have postblits.
Struct Destructors
Destructors are called when an object goes out of scope. Their purpose is to free up resources owned by the struct object.
Unions may not have fields that have destructors.
Struct Invariants
StructInvariant: invariant ( ) BlockStatement invariant BlockStatement
StructInvariants specify the relationships among the members of a struct instance. Those relationships must hold for any interactions with the instance from its public interface.
The invariant is in the form of a const member function. The invariant is defined to hold if all the AssertExpressions within the invariant that are executed succeed.
If the invariant does not hold, then the program enters an invalid state.
Any invariants for fields are applied before the struct invariant.
There may be multiple invariants in a struct. They are applied in lexical order.
StructInvariants must hold at the exit of the struct constructor (if any), and at the entry of the struct destructor (if any).
StructInvariants must hold at the entry and exit of all public or exported non-static member functions. The order of application of invariants is:
- preconditions
- invariant
- function body
- invariant
- postconditions
The invariant need not hold if the struct instance is implicitly constructed using the default .init value.
struct Date { this(int d, int h) { day = d; // days are 1..31 hour = h; // hours are 0..23 } invariant { assert(1 <= day && day <= 31); assert(0 <= hour && hour < 24); } private: int day; int hour; }
Public or exported non-static member functions cannot be called from within an invariant.
struct Foo { public void f() { } private void g() { } invariant { f(); // error, cannot call public member function from invariant g(); // ok, g() is not public } }
- Whether the StructInvariant is executed at runtime or not. This is typically controlled with a compiler switch.
- The behavior when the invariant does not hold is typically the same as for when AssertExpressions fail.
- Do not indirectly call exported or public member functions within a struct invariant, as this can result in infinite recursion.
- Avoid reliance on side effects in the invariant. as the invariant may or may not be executed.
- Avoid having mutable public fields of structs with invariants, as then the invariant cannot verify the public interface.
Identity Assignment Overload
While copy construction takes care of initializing an object from another object of the same type, or elaborate destruction is needed for the type, assignment is defined as copying the contents of one object over another, already initialized, type:
struct S { ... } // S has postblit or destructor S s; // default construction of s S t = s; // t is copy-constructed from s t = s; // t is assigned from s
Struct assignment t=s is defined to be semantically equivalent to:
t.opAssign(s);
where opAssign is a member function of S:
ref S opAssign(ref S s) { S tmp = this; // bitcopy this into tmp this = s; // bitcopy s into this tmp.__dtor(); // call destructor on tmp return this; }
An identity assignment overload is required for a struct if one or more of these conditions hold:
- it has a destructor
- it has a postblit
- it has a field with an identity assignment overload
If an identity assignment overload is required and does not exist, an identity assignment overload function of the type ref S opAssign(ref S) will be automatically generated.
A user-defined one can implement the equivalent semantics, but can be more efficient.
One reason a custom opAssign might be more efficient is if the struct has a reference to a local buffer:
struct S { int[] buf; int a; ref S opAssign(ref const S s) { a = s.a; return this; } this(this) { buf = buf.dup; } }
Here, S has a temporary workspace buf[]. The normal postblit will pointlessly free and reallocate it. The custom opAssign will reuse the existing storage.
Nested Structs
A nested struct is a struct that is declared inside the scope of a function or a templated struct that has aliases to local functions as a template argument. Nested structs have member functions. It has access to the context of its enclosing scope (via an added hidden field).
void foo() { int i = 7; struct SS { int x,y; int bar() { return x + i + 1; } } SS s; s.x = 3; s.bar(); // returns 11 }
A struct can be prevented from being nested by using the static attribute, but then of course it will not be able to access variables from its enclosing scope.
void foo() { int i = 7; static struct SS { int x, y; int bar() { return i; // error, SS is not a nested struct } } }
Unions and Special Member Functions
Unions may not have postblits, destructors, or invariants.