GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing dlang.org — Structs, Unions - D Programming Language Language Reference
Report a bug
If you spot a problem with this page, click here to create a GitHub issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page.
Requires a signed-in GitHub account. This works well for small changes.
If you'd like to make larger changes you may want to consider using
a local clone.
Structs, Unions Contents
OverviewWhereas classes are reference types, structs and unions are value types. StructsStructDeclaration:
struct Identifier ;
struct Identifier AggregateBody
StructTemplateDeclaration
AnonStructDeclaration
AnonStructDeclaration:
struct AggregateBody
AggregateBody:
{ DeclDefsopt }
Structs are simple aggregations of data and their associated operations on that data. The non-static data members of a struct are called fields. Members of a struct instance are accessed with the . operator. The following example declares a struct type with a single integer field: struct S { int i; } void main() { S a; // declare struct instance a.i = 3; S b = a; // copy a a.i++; assert(a.i == 4); assert(b.i == 3); } Assigning (or initializing) a struct instance from an lvalue will copy the original struct. A struct is defined to not have an identity; that is, the implementation is free to make bit copies of the struct as convenient. StorageFor local variables, a struct/union instance is allocated on the stack by default. To allocate on the heap, use new, which gives a pointer. A pointer to a struct or union is automatically
dereferenced when using the . operator to access members.
struct S { int i; } S* p = new S; S* q = p; p.i = 2; // `p.i` is the same as `(*p).i` assert(q.i == 2); // q points to the same struct instance as p See also: Struct Layout. Disabling newNewDeclaration:
new ( ) ;
new can be disallowed on an AggregateDeclaration with @disable new();: struct S { @disable new(); } void main() { S s; // OK S* p = new S; // Error, `new` is disabled } Note: Such an aggregate instance can still be
emplaced into a memory buffer.
UnionsUnionDeclaration:
union Identifier ;
union Identifier AggregateBody
UnionTemplateDeclaration
AnonUnionDeclaration
AnonUnionDeclaration:
union AggregateBody
A struct can contain multiple fields which are stored sequentially. Conversely, multiple fields in a union use overlapping storage. union U { ubyte i; char c; } void main() { U u; u.i = 3; assert(u.c == '\x03'); u.c++; assert(u.i == 4); } MembersThe following table shows which declarations a struct or union can contain:
Union LimitationsUnions may not have postblits, destructors, or invariants. A constructed field with a destructor may need to be destroyed manually. Recursive Structs and UnionsStructs and unions may not contain a non-static instance of themselves, however, they may contain a pointer to the same type. struct S { S* ptr; // OK S[] slice; // OK S s; // error S[2] array; // error static S global; // OK } Struct LayoutFields 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. extern(D) structs with no fields of non-zero size (aka Empty Structs) have a size of one byte. extern(C) struct C {} struct D {} static assert(C.sizeof == 0); static assert(D.sizeof == 1); Non-static function-nested D structs, which access the context of their enclosing scope, have an extra field. Implementation Defined:
Undefined Behavior:
Best Practices:
Bit Field DeclarationsA bit field is declared with a BitfieldDeclarator. The bit field width is specified by the AssignExpression or ConditionalExpression. It is evaluated at compile time, and must result in an integer that ranges from 0 to the number of bits in the type of the bit field. Bit fields can have an Initializer, which must evaluate to a compile-time constant and must fit within the width of the bit field. The type of the bit field must be an integral type, signed or unsigned. The values assigned to bit fields must fit into the specified width. The bit field is placed into a unit of memory at least as large as needed to hold a value of the type of the bit field. Anonymous bit fields do not have an Identifier for them. A zero width bit field must be anonymous. A zero width bit field causes the next bit field to be placed into the next unit. The address of a bit field cannot be taken. ref declarations cannot be initialized from bit fields. Arrays of bit fields are not allowed. Bit fields can only be fields in structs, unions and classes. Bit fields must be non-static. struct B { int x:3 = 2, y:2; } static assert(B.sizeof == 4); int vaporator(B b) { b.x = 4; b.y = 2; return b.x + b.y; // returns 6 } Implementation Defined: Reading and writing a bit field may cause reads and writes of other bit
fields in the same unit. Any use of bit fields where asynchronous access is possible will produce
unreliable results.
Implementation Defined: the layout of the bit fields is implementation defined. In practice, it is
expected to be identical to that of the associated C compiler, and ImportC.
Best Practices: If data portability is desired, use
std.bitmanip.bitfields.
If data compatibility with the associated C compiler is desired, use these bit fields.
Although not guaranteed, sticking with int and uint bit fields will yield the
most binary compatibility.
Plain Old DataA struct or union is Plain Old Data (POD) if it meets the following criteria:
Best Practices: Structs or unions that interface with C code should be POD.
Opaque Structs and UnionsOpaque struct and union declarations do not have an AggregateBody: struct S; union U; struct V(T); union W(T); 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 Best Practices: They can be used to implement the
PIMPL idiom.
InitializationDefault Initialization of StructsBy default, a struct field is initialized to its Initializer if one is supplied, otherwise the field is default initialized. struct S { int a = 4; int b; } S x; // x.a is set to 4, x.b to 0 Field Initializers are evaluated at compile time. For a nested struct, the context pointer is default initialized. Static Initialization of StructsStructInitializer:
{ StructMemberInitializersopt }
StructMemberInitializers:
StructMemberInitializer
StructMemberInitializer ,
StructMemberInitializer , StructMemberInitializers
StructMemberInitializer:
NonVoidInitializer
Identifier : NonVoidInitializer
If a StructInitializer is supplied, each StructMemberInitializer initializes a matching field:
Any field not covered by a StructMemberInitializer is default initialized. struct S { int a, b, c, d = 7; } S r; // r.a = 0, r.b = 0, r.c = 0, r.d = 7 S s = { a:1, b:2 }; // s.a = 1, s.b = 2, s.c = 0, s.d = 7 S t = { c:4, b:5, a:2, d:5 }; // t.a = 2, t.b = 5, t.c = 4, t.d = 5 S u = { 1, 2 }; // u.a = 1, u.b = 2, u.c = 0, u.d = 7 S v = { 1, d:3 }; // v.a = 1, v.b = 0, v.c = 0, v.d = 3 S w = { b:1, 3 }; // w.a = 0, w.b = 1, w.c = 3, w.d = 7 Initializing a field more than once is an error: S x = { 1, a:2 }; // error: duplicate initializer for field `a`
Default Initialization of UnionsUnions are by default initialized to whatever the Initializer for the first field is, and if none is supplied, to the default initializer for the first field's type. If the union is larger than the first field, the remaining bits are set to 0. union U { int a = 4; long b; } U x; // x.a is set to 4, x.b to an implementation-defined value It is an error to supply initializers for members other than the first one. union V { int a; long b = 4; } // error: union field `b` with default initialization `4` must be before field `a` union W { int a = 4; long b = 5; } // error: overlapping default initialization for `a` and `b` The default initializer is evaluated at compile time. Implementation Defined: The values the fields other than the
default initialized field are set to.
Static Initialization of UnionsUnions are initialized similarly to structs, except that only one member initializer is allowed. If the member initializer does not specify an identifier, it will initialize the first member of the union. union U { int a; double b; } U u = { 2 }; // u.a = 2 U v = { b : 5.0 }; // v.b = 5.0 U w = { 2, 3 }; // error: overlapping initialization for field `a` and `b`
If the union is larger than the initialized field, the remaining bits are set to 0. Implementation Defined: The values the fields other than the
initialized field are set to.
Dynamic Initialization of StructsThe 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 the struct has a constructor, and the struct is initialized with a value that is of a different type, then the constructor is called: struct S { int a; this(int v) { this.a = v; } } S s = 3; // sets s.a to 3 using S's constructor If the struct does not have a constructor but 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 Dynamic Initialization of UnionsThe static initializer syntax can also be used to initialize non-static variables. The initializer need not be evaluable at compile time. union U { int a; double b; } void test(int i) { U u = { a : i }; // u.a = i U v = { b : 5.0 }; // v.b = 5.0 } Struct LiteralsA struct literal consists of the name of the struct followed by a parenthesized NamedArgumentList: struct S { int x; float y; } S s1 = S(1, 2); // set field x to 1, field y to 2 S s2 = S(y: 2, x: 1); // same as above assert(s1 == s2); If a struct has a constructor or a member function named opCall, then struct literals for that struct are not possible. See also opCall operator overloading for the issue workaround. Struct literals are syntactically like function calls. Arguments are assigned to fields as follows:
Note: These rules are consistent with function calls, see
Matching Arguments to Parameters.
struct S { int x = 1, y = 2, z = 3; } S s0 = S(y: 5, 6, x: 4); // `6` is assigned to field `z`, which comes after `y` assert(s0.z == 6); S s1 = S(y: 5, z: 6); // Field x is not assigned, set to default initializer `1` assert(s1.x == 1); //S s2 = S(y: 5, x: 4, 5); // Error: field `y` is assigned twice //S s3 = S(z: 2, 3); // Error: no field beyond `z` If there is a union field in the struct, only one member of the union can be initialized inside a struct literal. This matches the behaviour for union literals. Union LiteralsA union literal is like a struct literal, but only one field can be initialized in the NamedArgumentList. To initialize a field other than the first, a NamedArgument must be used. Any remainder of the union's memory is initialized to zero. union U { byte a; // overlaps with `b[0]` char[2] b; } U u = U(2); // construct `u.a` assert(u.a == 2); assert(u.b == [2, 0]); u = U(b: 1); assert(u.a == 1); assert(u.b == [1, 1]); Anonymous Structs and UnionsAn anonymous struct or union can be declared as a member of a parent class, struct or union by omitting the identifier after struct or union. An anonymous struct declares sequentially stored fields in the parent type. An anonymous union declares overlapping fields in the parent type. An anonymous union is useful inside a class or struct to share memory for fields, without having to name a parent field with a separate union type. struct S { int a; union { byte b; char c; } } S s = S(1, 2); assert(s.a == 1); assert(s.b == 2); assert(s.c == 2); // overlaps with `b` Conversely, an anonymous struct is useful inside a union to declare multiple fields that are stored sequentially. union U { int a; struct { uint b; bool c; } } U u = U(1); assert(u.a == 1); assert(u.b == 1); // overlaps with `a` assert(u.c == false); // no overlap Struct Properties
Struct Field Properties
Const, Immutable and Shared StructsA 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 } Union ConstructorsUnions are constructed in the same way as structs. Struct ConstructorsStruct constructors are used to initialize an instance of a struct when a more complex construction is needed than is allowed by static initialization or a struct literal. Constructors are defined with a function name of this and have no return value. The grammar is the same as for the class Constructor. A struct constructor is called by the name of the struct followed by Parameters. If the ParameterList is empty, the struct instance is default initialized. struct S { int x, y = 4, z = 6; this(int a, int b) { x = a; y = b; } } void main() { S a = S(4, 5); // calls S.this(4, 5): a.x = 4, a.y = 5, a.z = 6 S b = S(); // default initialized: b.x = 0, b.y = 4, b.z = 6 S c = S(1); // error, matching this(int) not found } Named arguments will be forwarded to the constructor and match parameter names, not struct field names. struct S { int x; int y; this(int y, int z) { this.x = y; this.y = z; } } S a = S(x: 3, y: 4); // Error: constructor has no parameter named `x` S b = S(y: 3, 4); // `y: 3` will set field `x` through parameter `y` A default constructor (i.e. one with an empty ParameterList) is not allowed. struct S { int x; this() { } // error, struct default constructor not allowed } Delegating ConstructorsA constructor can call another constructor for the same struct in order to share common initializations. This is called a delegating constructor: struct S { int j = 1; long k = 2; this(long k) { this.k = k; } this(int i) { // At this point: j=1, k=2 this(6L); // delegating constructor call // At this point: j=1, k=6 j = i; // At this point: j=i, k=6 } } The following restrictions apply:
See also: delegating class constructors. Struct InstantiationWhen an instance of a struct is created, the following steps happen:
Constructor AttributesA constructor can be disabled. A qualified constructor (const, immutable or shared) constructs the object instance 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); // error // Mutable constructor cannot construct immutable object. auto x1 = immutable S1(1); // error // Immutable constructor creates immutable object. immutable i2 = immutable S2(1); // Immutable constructor cannot construct mutable object. auto x2 = S2(1); // error // Constructed immutable object is not implicitly convertible to mutable. S2 m2 = immutable S2(1); // error // Constructed immutable object is implicitly convertible to const. const S2 c2 = immutable S2(1); } Constructors can be overloaded with different qualifiers. struct S { this(int); // non-shared mutable constructor this(int) shared; // shared mutable constructor this(int) immutable; // immutable constructor } void fun() { S m = S(1); shared s = shared S(2); immutable i = immutable S(3); } Pure ConstructorsIf the constructor can create a unique object (i.e. if it is pure), the object is implicitly convertible to any qualifiers. struct S { this(int) pure; // Based on the definition, this creates a mutable object. But the // created object cannot contain any mutable global data. // Therefore the created object is unique. this(int[] arr) immutable pure; // Based on the definition, this creates an immutable object. But // the argument int[] never appears in the created object so it // isn't implicitly convertible to immutable. Also, it cannot store // any immutable global data. // Therefore the created object is unique. } void fun() { immutable i = immutable S(1); // this(int) pure is called shared s = shared S(1); // this(int) pure is called S m = S([1,2,3]); // this(int[]) immutable pure is called } Disabling Default InitializationIf a struct constructor is annotated with @disable and has an empty ParameterList, the struct has disabled default initialization. The only way it can be constructed is via a call to another constructor with a non-empty ParameterList. If there are no other valid constructors, it cannot be instantiated other than via a VoidInitializer. A disabled constructor may not have a FunctionBody. If any fields have disabled default initialization, struct default initialization is also disabled. struct S { int x; // Disables default initialization @disable this(); this(int v) { x = v; } } struct T { int y; S s; } void main() { S s; // error: default initialization is disabled S t = S(); // error: also disabled S u = S(1); // constructed by calling `S.this(1)` S v = void; // not initialized, but allowed S w = { 1 }; // error: cannot use { } since constructor exists S[3] a; // error: default initialization is disabled S[3] b = [S(1), S(20), S(-2)]; // ok T t; // error: default initialization is disabled } Best Practices: Disabling default initialization is useful when the default value
of a field, such as null, is not acceptable.
Field initialization inside a constructorIn a constructor body, if a delegating constructor is called, all field assignments are considered assignments. Otherwise, the first instance of field assignment is its initialization, and assignments of the form field = expression are treated as equivalent to typeof(field)(expression). The values of fields may be read before initialization or construction with a delegating constructor. struct S { int num; int ber; this(int i) { num = i + 1; // initialization num = i + 2; // assignment ber = ber + 1; // ok to read before initialization } this(int i, int j) { this(i); num = i + 1; // assignment } } If the field type has an opAssign method, it will not be used for initialization. struct A { this(int n) {} void opAssign(A rhs) {} } struct S { A val; this(int i) { val = A(i); // val is initialized to the value of A(i) val = A(2); // rewritten to val.opAssign(A(2)) } } If the field type is not mutable, multiple initialization will be rejected. struct S { immutable int num; this(int) { num = 1; // OK num = 2; // Error: assignment to immutable } } If the field is initialized on one path, it must be initialized on all paths. struct S { immutable int num; immutable int ber; this(int i) { if (i) num = 3; // initialization else num = 4; // initialization } this(long j) { j ? (num = 3) : (num = 4); // ok j || (ber = 3); // Error: initialized on only one path j && (ber = 3); // Error: initialized on only one path } } A field initialization may not appear in a loop or after a label. struct S { immutable int num; immutable string str; this(int j) { foreach (i; 0..j) { num = 1; // Error: field initialization not allowed in loops } size_t i = 0; Label: str = "hello"; // Error: field initialization not allowed after labels if (i++ < 2) goto Label; } this(int j, int k) { switch (j) { case 1: ++j; break; default: break; } num = j; // Error: `case` and `default` are also labels } } If a field's type has disabled default construction, then it must be initialized in the constructor. struct S { int y; @disable this(); } struct T { S s; this(S t) { s = t; } // ok this(int i) { this('c'); } // ok this(char) { } // Error: s not initialized } Struct Copy ConstructorsWarning: The plan is for copy constructors to replace postblit constructors, leaving postblit constructors only in legacy code. However, because the compiler hooks in druntime which deal with dynamic arrays and associative arrays have not yet all been updated to support copy constructors properly (issue #20970), any type that might be used in a dynamic array or associative array should use a postblit constructor rather than a copy constructor. The copy constructor will not be called in all the cases where it should be for the elements of a dynamic array or the keys or values in an associative array. Postblit constructors do not have this problem. For backward compatibility reasons, a struct that explicitly defines both a copy constructor and a postblit will only use the postblit for implicit copying. However, if the postblit is disabled, the copy constructor will be used. If a struct defines a copy constructor (user-defined or generated) and has fields that define postblits, a deprecation will be issued, informing that the postblit will have priority over the copy constructor. Copy constructors are used to initialize a struct instance from another instance of the same type. A struct that defines a copy constructor is not POD. A constructor declaration is a copy constructor declaration if it meets the following requirements:
struct A { this(ref return scope A rhs) {} // copy constructor this(ref return scope const A rhs, int b = 7) {} // copy constructor with default parameter } The copy constructor is type checked as a normal constructor. If a copy constructor is defined, implicit calls to it will be inserted in the following situations:
struct A { int[] arr; this(ref return scope A rhs) { arr = rhs.arr.dup; } } void main() { A a; a.arr = [1, 2]; A b = a; // copy constructor gets called b.arr[] += 1; assert(a.arr == [1, 2]); // a is unchanged assert(b.arr == [2, 3]); } struct A { this(ref return scope A another) {} } void fun(A a) {} void main() { A a; fun(a); // copy constructor gets called } struct A { this(ref return scope A another) {} } A fun() { A a; return a; // NRVO, no copy constructor call } A a; A gun() { return a; // cannot perform NRVO, rewrite to: return (A __tmp; __tmp.copyCtor(a)); } void main() { A a = fun(); A b = gun(); } Disabled CopyingWhen a copy constructor is defined for a struct (or marked @disable), the compiler no longer implicitly generates default copy/blitting constructors for that struct: struct A { int[] a; this(ref return scope A rhs) {} } void fun(immutable A) {} void main() { immutable A a; fun(a); // error: copy constructor cannot be called with types (immutable) immutable } struct A { @disable this(ref A); } void main() { A a; A b = a; // error: copy constructor is disabled } If a union U has fields that define a copy constructor, whenever an object of type U is initialized by copy, an error will be issued. The same rule applies to overlapped fields (anonymous unions). struct S { this(ref S); } union U { S s; } void main() { U a; U b = a; // error, could not generate copy constructor for U } Copy Constructor AttributesThe copy constructor can be overloaded with different qualifiers applied to the parameter (copying from a qualified source) or to the copy constructor itself (copying to a qualified destination): struct A { this(ref return scope A another) {} // 1 - mutable source, mutable destination this(ref return scope immutable A another) {} // 2 - immutable source, mutable destination this(ref return scope A another) immutable {} // 3 - mutable source, immutable destination this(ref return scope immutable A another) immutable {} // 4 - immutable source, immutable destination } void main() { A a; immutable A ia; A a2 = a; // calls 1 A a3 = ia; // calls 2 immutable A a4 = a; // calls 3 immutable A a5 = ia; // calls 4 } The inout qualifier may be applied to the copy constructor parameter in order to specify that mutable, const, or immutable types are treated the same: struct A { this(ref return scope inout A rhs) immutable {} } void main() { A r1; const(A) r2; immutable(A) r3; // All call the same copy constructor because `inout` acts like a wildcard immutable(A) a = r1; immutable(A) b = r2; immutable(A) c = r3; } Implicit Copy ConstructorsA copy constructor is generated implicitly by the compiler for a struct S if all of the following conditions are met:
If the restrictions above are met, the following copy constructor is generated: this(ref return scope inout(S) src) inout { foreach (i, ref inout field; src.tupleof) this.tupleof[i] = field; } If the generated copy constructor fails to type check, it will receive the @disable attribute. Struct Move ConstructorsMove constructors are very much like copy constructors. The difference is that copy constructors make a copy of the original, while move constructors move the contents of the original, and the lifetime of the original ends. Note: Do not use postblits in the same struct with move constructors.
If a move constructor is declared, also declare a copy constructor. A constructor declaration is a move constructor declaration if it meets the following requirements:
struct A { this(ref return scope A rhs) {} // copy constructor this(return scope A rhs) {} // move constructor this(return scope const A rhs, int b = 7) {} // move constructor with default parameter } The move constructor is type checked as a normal constructor. The move constructor's first parameter only accepts rvalues. An lvalue can be coerced into being an rvalue using __rvalue(Expression). If a move constructor is defined, implicit calls to it will be inserted in the following situations:
struct A { int[] arr; this(return scope A rhs) // move constructor { arr = rhs.arr; rhs.arr = null; // do not leave dangling reference to array } this(ref return scope A rhs) { assert(0); } } void main() { A a; a.arr = [1, 2]; A b = __rvalue(a); // move constructor gets called b.arr[] += 1; assert(a.arr is null); // a.arr is gone assert(b.arr == [2, 3]); } struct A { this(return scope A another) {} // move constructor this(ref return scope A rhs) { assert(0); } } void fun(A a) {} void main() { A a; fun(__rvalue(a)); // move constructor gets called // `a` is no longer a valid object } Disabled MovingWhen a move constructor is defined for a struct (or marked @disable), the compiler no longer implicitly generates default move constructors for that struct: struct A { this(ref A); @disable this(A); } void main() { A a; A b = __rvalue(a); // error: move constructor is disabled } If a union U has fields that define a move constructor, whenever an object of type U is initialized by move, an error will be issued. The same rule applies to overlapped fields (anonymous unions). struct S { this(ref S); this(S); } union U { S s; } void main() { U a; U b = __rvalue(a); // error, could not generate move constructor for U } Move Constructor AttributesThe move constructor can be overloaded with different qualifiers applied to the parameter (moving from a qualified source) or to the move constructor itself (moving to a qualified destination): struct A { this(ref return scope A another) { assert(0); } // copy constructor this(return scope A another) {} // 1 - mutable source, mutable destination this(return scope immutable A another) {} // 2 - immutable source, mutable destination this(return scope A another) immutable {} // 3 - mutable source, immutable destination this(return scope immutable A another) immutable {} // 4 - immutable source, immutable destination } void main() { A a; immutable A ia; A a2 = __rvalue(a); // calls 1 A a3 = __rvalue(ia); // calls 2 A b; immutable A ib; immutable A b4 = __rvalue(b); // calls 3 immutable A b5 = __rvalue(ib); // calls 4 } The inout qualifier may be applied to the move constructor parameter in order to specify that mutable, const, or immutable types are treated the same: struct A { this(ref return scope inout A rhs) immutable { assert(0); } this(return scope inout A rhs) immutable {} } void main() { A r1; const(A) r2; immutable(A) r3; // All call the same move constructor because `inout` acts like a wildcard immutable(A) a = __rvalue(r1); immutable(A) b = __rvalue(r2); immutable(A) c = __rvalue(r3); } Implicit Move ConstructorsA move constructor is generated implicitly by the compiler for a struct S if all of the following conditions are met:
If the restrictions above are met, the following move constructor is generated: this(return scope inout(S) src) inout { foreach (i, ref inout field; src.tupleof) this.tupleof[i] = __rvalue(field); } If the generated move constructor fails to type check, it will receive the @disable attribute. import core.stdc.stdio; struct T { int i; inout this(ref inout T t) { this.i = t.i - 1; printf("this(ref T)\n"); } inout this(inout T t) { this.i = t.i + 1; printf("this(T)\n"); } } struct S { T t; } void main() { S s; s.t.i = 3; S u = s; printf("u.t.i = %d\n", u.t.i); assert(u.t.i == 2); S v = __rvalue(u); printf("v.t.i = %d\n", v.t.i); assert(v.t.i == 3); } Struct PostblitsPostblit:
this ( this ) MemberFunctionAttributesopt FunctionBody
this ( this ) MemberFunctionAttributesopt MissingFunctionBody
Warning: The plan is for copy constructors to replace postblit constructors, leaving postblit constructors only in legacy code. However, because the compiler hooks in druntime which deal with dynamic arrays and associative arrays have not yet all been updated to support copy constructors properly (issue #20970), any type that might be used in a dynamic array or associative array should use a postblit constructor rather than a copy constructor. The copy constructor will not be called in all the cases where it should be for the elements of a dynamic array or the keys or values in an associative array. Postblit constructors do not have this problem. For backward compatibility reasons, a struct that explicitly defines both a copy constructor and a postblit will only use the postblit for implicit copying. However, if the postblit is disabled, the copy constructor will be used. If a struct defines a copy constructor (user-defined or generated) and has fields that define postblits, a deprecation will be issued, informing that the postblit will have priority over the copy constructor. Copy construction is defined as initializing a struct instance from another instance of the same type. Copy construction is divided into two parts:
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 } Depending on the struct layout, the compiler may generate the following internal postblit functions:
// struct with alias __xpostblit = __postblit struct X { this(this) {} } // struct with alias __xpostblit = __fieldPostblit // which contains a call to X.__xpostblit struct Y { X a; } // struct with alias __xpostblit = __aggrPostblit which contains // a call to Y.__xpostblit and a call to Z.__postblit struct Z { Y a; this(this) {} } void main() { // X has __postblit and __xpostblit (pointing to __postblit) static assert(__traits(hasMember, X, "__postblit")); static assert(__traits(hasMember, X, "__xpostblit")); // Y does not have __postblit, but has __xpostblit (pointing to __fieldPostblit) static assert(!__traits(hasMember, Y, "__postblit")); static assert(__traits(hasMember, Y, "__xpostblit")); // __fieldPostblit is not a member of the struct static assert(!__traits(hasMember, Y, "__fieldPostblit")); // Z has __postblit and __xpostblit (pointing to __aggrPostblit) static assert(__traits(hasMember, Z, "__postblit")); static assert(__traits(hasMember, Z, "__xpostblit")); // __aggrPostblit is not a member of the struct static assert(!__traits(hasMember, Z, "__aggrPostblit")); } Neither of the above postblits is defined for structs that don't define this(this) and don't have fields that transitively define it. If a struct does not define a postblit (implicit or explicit) but defines functions that use the same name/signature as the internally generated postblits, the compiler is able to identify that the functions are not actual postblits and does not insert calls to them when the struct is copied. Example: struct X {} int a; struct Y { int a; X b; void __fieldPostPostblit() { a = 42; } } void main() { static assert(!__traits(hasMember, X, "__postblit")); static assert(!__traits(hasMember, X, "__xpostblit")); static assert(!__traits(hasMember, Y, "__postblit")); static assert(!__traits(hasMember, Y, "__xpostblit")); Y y; auto y2 = y; assert(a == 0); // __fieldPostBlit does not get called } Postblits cannot be overloaded. If two or more postblits are defined, even if the signatures differ, the compiler assigns the __postblit name to both and later issues a conflicting function name error: struct X { this(this) {} this(this) const {} // error: function X.__postblit conflicts with function X.__postblit } The following describes the behavior of the qualified postblit definitions:
struct S { int n; this(this) const { import std.stdio : writeln; writeln("postblit called"); //++n; // error: cannot modify this.n in `const` function } } void main() { S s1; auto s2 = s1; const S s3; auto s4 = s3; immutable S s5; auto s6 = s5; } struct Y { // not invoked anywhere, no error is issued this(this) immutable { } } struct S { this(this) immutable { } } void main() { S s1; auto s2 = s1; // error: immutable method `__postblit` is not callable using a mutable object const S s3; auto s4 = s3; // error: immutable method `__postblit` is not callable using a mutable object immutable S s5; auto s6 = s5; // error: immutable method `__postblit` is not callable using a mutable object } struct S { this(this) shared { } } void main() { S s1; auto s2 = s1; // error: shared method `__postblit` is not callable using a non-shared object const S s3; auto s4 = s3; // error: shared method `__postblit` is not callable using a non-shared object immutable S s5; auto s6 = s5; // error: shared method `__postblit` is not callable using a non-shared object // calling the shared postblit on a shared object is accepted shared S s7; auto s8 = s7; } An unqualified postblit will get called even if the struct is instantiated as immutable or const, but the compiler issues an error if the struct is instantiated as shared: struct S { int n; this(this) { ++n; } } void main() { immutable S a; // shared S a; => error : non-shared method is not callable using a shared object auto a2 = a; import std.stdio: writeln; writeln(a2.n); // prints 1 } From a postblit perspective, qualifiying the struct definition yields the same result as explicitly qualifying the postblit. The following table lists all the possibilities of grouping qualifiers for a postblit associated with the type of object that needs to be used in order to successfully invoke the postblit:
Note that when const and immutable are used to explicitly qualify a postblit as in this(this) const immutable; or const immutable this(this); - the order in which the qualifiers are declared does not matter - the compiler generates a conflicting attribute error, however declaring the struct as const/immutable and the postblit as immutable/const achieves the effect of applying both qualifiers to the postblit. In both cases the postblit is qualified with the more restrictive qualifier, which is immutable. The postblits __fieldPostblit and __aggrPostblit are generated without any implicit qualifiers and are not considered struct members. This leads to the situation where qualifying an entire struct declaration with const or immutable does not have any impact on the above-mentioned postblits. However, since __xpostblit is a member of the struct and an alias of one of the other postblits, the qualifiers applied to the struct will affect the aliased postblit. struct S { this(this) { } } // `__xpostblit` aliases the aggregated postblit so the `const` applies to it. // However, the aggregated postblit calls the field postblit which does not have // any qualifier applied, resulting in a qualifier mismatch error const struct B { S a; // error : mutable method B.__fieldPostblit is not callable using a const object this(this) { } } // `__xpostblit` aliases the field postblit; no error const struct B2 { S a; } // Similar to B immutable struct C { S a; // error : mutable method C.__fieldPostblit is not callable using a immutable object this(this) { } } // Similar to B2, compiles immutable struct C2 { S a; } In the above situations the errors do not contain line numbers because the errors are regarding generated code. Qualifying an entire struct as shared correctly propagates the attribute to the generated postblits: shared struct A { this(this) { import std.stdio : writeln; writeln("the shared postblit was called"); } } struct B { A a; } void main() { shared B b1; auto b2 = b1; } Unions may have fields that have postblits. However, a union itself never has a postblit. Copying a union does not result in postblit calls for any fields. If those calls are desired, they must be inserted explicitly by the programmer: struct S { int count; this(this) { ++count; } } union U { S s; } void main() { U a = U.init; U b = a; assert(b.s.count == 0); b.s.__postblit; assert(b.s.count == 1); } Member Functions (a.k.a. Methods)A struct/union can have non-static member functions, like classes. Such functions (called instance methods) have a hidden this parameter which is a reference to the struct instance. A method can have MemberFunctionAttributes, e.g. immutable methods. An instance method can still be called on an rvalue struct instance, even if the method is not const: struct S { int i; int f() => ++i; } void main() { //S().i++; // cannot modify, `S().i` is not an lvalue assert(S().f() == 1); // OK } Rationale: An instance method may have other side effects besides mutating a field,
or it may produce a useful return value. In the general case, throwing
away changes to a field after the method returns does not necessarily indicate
a logic error.
Struct DestructorsDestructors are called implicitly when an object goes out of scope, or before an assignment (by default). Their purpose is to free up resources owned by the struct object. struct S { int i; ~this() { import std.stdio; writeln("S(", i, ") is being destructed"); } } void main() { auto s1 = S(1); { auto s2 = S(2); // s2 destructor called } S(3); // s3 destructor called // s1 destructor called } If the struct has a field of another struct type which itself has a destructor, that destructor will be called at the end of the parent destructor. If there is no parent destructor, the compiler will generate one. Similarly, a static array of a struct type with a destructor will have the destructor called for each element when the array goes out of scope. struct S { char c; ~this() { import std.stdio; writeln("S(", c, ") is being destructed"); } } struct Q { S a; S b; } void main() { Q q = Q(S('a'), S('b')); S[2] arr = [S('0'), S('1')]; // destructor called for arr[1], arr[0], q.b, q.a } A destructor for a struct instance can also be called early using destroy. Note that the destructor will still be called again when the instance goes out of scope. Struct destructors are used for RAII. Union Field DestructionUnions may have fields that have destructors. However, a union itself never has a destructor. When a union goes out of scope, destructors for its fields are not called. If those calls are desired, they must be inserted explicitly by the programmer: struct S { ~this() { import std.stdio; writeln("S is being destructed"); } } union U { S s; } void main() { import std.stdio; { writeln("entering first scope"); U u = U.init; scope (exit) writeln("exiting first scope"); } { writeln("entering second scope"); U u = U.init; scope (exit) { writeln("exiting second scope"); destroy(u.s); } } } Struct InvariantsInvariant:
invariant ( ) BlockStatement
invariant BlockStatement
invariant ( AssertArguments ) ;
Struct Invariants 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. 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; } There may be multiple invariants in a struct. They are applied in lexical order. Struct Invariants must hold at the exit of the struct constructor (if any), and at the entry of the struct destructor (if any). Struct Invariants must hold at the entry and exit of all public or exported non-static member functions. The order of application of invariants is:
The invariant need not hold if the struct instance is implicitly constructed using the default .init value. If the invariant does not hold, then the program enters an invalid state. Implementation Defined:
Undefined Behavior: happens if the invariant does not hold and execution continues.
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 } } Best Practices:
Identity Assignment OverloadWhile copy construction takes care of initializing an object from another object of the same type, assignment is defined as copying the contents of a source object over those of a destination object, calling the destination object's destructor if it has one in the process: 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. An identity assignment overload is required for a struct if one or more of these conditions hold:
If an identity assignment overload is required and does not exist, an identity assignment overload function will be automatically generated with the following lowering: 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; } 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) return { a = s.a; return this; } this(this) { buf = buf.dup; } } Here, S has a temporary workspace buf[]. A compiler-generated opAssign would pointlessly free and reallocate it. The custom opAssign will reuse the existing storage. Alias ThisAliasThis:
alias Identifier this ;
alias this = Identifier ;
An AliasThis declaration names a member to subtype. The Identifier names that member. A struct or union instance can be implicitly converted to the AliasThis member. struct S { int x; alias x this; } int foo(int i) { return i * 2; } void main() { S s; s.x = 7; int i = -s; assert(i == -7); i = s + 8; assert(i == 15); i = s + s; assert(i == 14); i = 9 + s; assert(i == 16); i = foo(s); // implicit conversion to int assert(i == 14); } If the member is a class or struct, undefined lookups will be forwarded to the AliasThis member. class Foo { int baz = 4; int get() { return 7; } } struct Bar { Foo foo; alias foo this; } void main() { Bar bar = Bar(new Foo()); int i = bar.baz; assert(i == 4); i = bar.get(); assert(i == 7); } If the Identifier refers to a property member function with no parameters then conversions and undefined lookups are forwarded to the return value of the function. struct S { int x; @property int get() { return x * 2; } alias get this; } void main() { S s; s.x = 2; int i = s; assert(i == 4); } If a struct declaration defines an opCmp or opEquals method, it will take precedence to that of the AliasThis member. Note that, unlike an opCmp method, an opEquals method is implicitly defined for a struct declaration if a user-defined one isn't provided. This means that if the AliasThis member's opEquals should be used, it must be explicitly defined: struct S { int a; bool opEquals(S rhs) const { return this.a == rhs.a; } } struct T { int b; S s; alias s this; } void main() { S s1, s2; T t1, t2; assert(s1 == s2); // calls S.opEquals assert(t1 == t2); // calls compiler generated T.opEquals that implements member-wise equality assert(s1 == t1); // calls s1.opEquals(t1.s); assert(t1 == s1); // calls t1.s.opEquals(s1); } struct U { int a; bool opCmp(U rhs) const { return this.a < rhs.a; } } struct V { int b; U u; alias u this; } void main() { U u1, u2; V v1, v2; assert(!(u1 < u2)); // calls U.opCmp assert(!(v1 < v2)); // calls U.opCmp because V does not define an opCmp method // so the alias this of v1 is employed; U.opCmp expects a // parameter of type U, so alias this of v2 is used assert(!(u1 < v1)); // calls u1.opCmp(v1.u); assert(!(v1 < u1)); // calls v1.u.opCmp(v1); } Attributes are ignored for AliasThis. A struct/union may only have a single AliasThis member. Nested StructsA struct is a nested struct if:
A nested struct can have member functions. It has access to the context of its enclosing scope via a 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 } The static attribute will prevent a struct from being nested. As such, the struct will not have access to 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 } } } Warning: For nested structs, .init is not the same as default initialization. Copyright © 1999-2026 by the D Language Foundation | Page generated by
Ddoc on Mon Nov 9 05:59:19 2026
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |