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 local clone. Page wiki View or edit the community-maintained wiki page associated with this page.

Structs, Unions

Whereas classes are reference types, structs are value types. Any C struct can be exactly represented as a D struct, except non-static function-nested D structs which access the context of their enclosing scope. Structs and unions are meant as simple aggregations of data, or as a way to paint a data structure over hardware or an external type. External types can be defined by the operating system API, or by a file format. Object oriented features are provided with the class data type.

A struct is defined to not have an identity; that is, the implementation is free to make bit copies of the struct as convenient.

Struct, Class Comparison Table
FeaturestructclassC structC++ structC++ class
value type
reference type
data members
hidden members
static members
default member initializers
bit fields
non-virtual member functions
virtual member functions
constructors
postblit/copy constructors
destructors
SharedStaticConstructors
SharedStaticDestructors
RAII
assign overload
literals
operator overloading
inheritance
invariants
unit tests
synchronizable
parameterizable
alignment control
member protection
default public
tag name space
anonymous
static constructor
static destructor
const/immutable/shared
inner nesting
AggregateDeclaration:
    struct Identifier StructBody
    union Identifier StructBody
    struct Identifier ;
    union Identifier ;
    StructTemplateDeclaration
    UnionTemplateDeclaration

StructBody:
    { }
    { StructBodyDeclarations }

StructBodyDeclarations:
    StructBodyDeclaration
    StructBodyDeclaration StructBodyDeclarations

StructBodyDeclaration:
    DeclDef
    StructAllocator
    StructDeallocator
    StructPostblit
    AliasThis

StructAllocator:
    ClassAllocator

StructDeallocator:
    ClassDeallocator

They work like they do in C, with the following exceptions:

Opaque Structs and Unions

Opaque struct and union declarations do not have a StructBody:

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 supplied, to the default initializer for the member's type. If a static initializer is supplied, the members are initialized by the member name, colon, 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; int b; int c; int 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

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

The static initializer syntax can also be used to initialize non-static variables, provided that the member names are not given. The initializer need not be evaluatable at compile time.

void test(int i) {
  S q = { 1, i }; // q.a = 1, q.b = i, 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

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)
  { S s;
    s.a = v.a + 1;
    return s;
  }
}

S s = 3; // sets s.a to 3
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. 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

Struct Properties
NameDescription
.sizeofSize in bytes of struct
.alignofSize boundary struct needs to be aligned on
.tupleofGets type tuple of fields

Struct Field Properties

Struct Field Properties
NameDescription
.offsetofOffset in bytes of field from beginning of struct

Const and Immutable 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 is const
  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;
}

Struct Postblits

StructPostblit:
    this(this) FunctionBody

Copy construction is defined as initializing a struct instance from another struct of the same type. Copy construction is divided into two parts:

  1. blitting the fields, i.e. copying the bits
  2. 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;
  }
  ~this() {
    delete a;
  }
}

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.

Assignment Overload

While copy construction takes care of initializing an object from another object of the same type, assignment is defined as copying the contents of one object over another, already initialized, type:

struct S { ... }
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:

S* opAssign(ref const S s)
{   ... bitcopy *this into tmp ...
    ... bitcopy s into *this ...
    ... call destructor on tmp ...
    return this;
}

While the compiler will generate a default opAssign as needed, a user-defined one can be supplied. The user-defined one must still 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;

  S* opAssign(ref const S s) {
    a = s.a;
    return this;
  }

  this(this) {
    buf = buf.dup;
  }

  ~this() {
    delete buf;
  }
}

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
    }
  }
}

A templated struct can become a nested struct if it has a local function passed as an aliased argument:

struct A(alias F) {
  int fun(int i) { return F(i); }
}

A!(F) makeA(alias F)() {return A!(F)(); }

void main() {
  int x = 40;
  int fun(int i) { return x + i; }
  A!(fun) a = makeA!(fun)();
  a.fun(2);
}
Forums | Comments | Search | Downloads | Home