Report a bug
If you spot a problem with this page, click here to create a Bugzilla 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.

Qualified constructors and destructors for structs

Version 1
Created 2011-04-20
StatusDraft
Last modified 2011-04-20
Author Andrei Alexandrescu

Abstract

This is a design Walter and I conceived a long time ago but he never got around to implementing it. Because of this delay many structs are near unusable with qualifier. In brief, constructors and destructors are overloadable on qualifiers.

Rationale

Creating immutable objects must observe quite different (and more strict) rules than creating mutable objects.

Description

Copy construction of qualified structs

If a struct does not define this(this), bitblitting works for copying around objects of the same type and same qualifier. Also, bitblitting works for copying a mutable or immutable object into a const one. In addition, a reference to a mutable or immutable object can be always implicitly converted to a reference to a const object.

If a struct does not define this(this) _and_ has no mutable aliasing, bitblitting converts from any qualifier to any other qualifier.

If a struct defines this(this) _and_ has no mutable aliasing, that copy constructor will be called to copy a qualified object to an object of a different qualifier. Example:

struct Point {
  int x, y, z;
  this(this) { writeln("postblit"); }
}
unittest {
  Point p1;
  auto p2 = const Point(p1); // postblit called
  auto p3 = immutable Point(p1); // postblit called
  auto p4 = immutable Point(p2); // postblit called
}

If a type with mutable aliasing defines this(this), the rules are more restrictive. That constructor can only copy unqualified objects. To copy qualified objects, qualified copy constructors may be defined:

struct Point {
  int x, y, z;
  this(this) { writeln("mutable <- mutable"); }
  this(this) const { writeln("const <- const"); }
  this(this) immutable { writeln("immutable <- immutable"); }
}

unittest {
  Point m;
  const Point c;
  immutable Point i;
  auto mcopy = m; // mutable <- mutable
  auto ccopy = c; // const <- const
  auto icopy = i; // immutable <- immutable
}

To copy an object with mutable aliasing from an object with the same type but different qualifiers, just define a regular constructor with the appropriate qualifications:

struct Point {
  int x, y, z;
  this(const Point rhs) { writeln("mutable <- const"); }
}

unittest {
  const Point c;
  Point m = c; // mutable <- const
  immutable Point i;
  Point m = i; // mutable <- const
}

This example also illustrates that conversion to const is implicit if not defined: a mutable or immutable object can be converted to a const object by means of bitblitting. The latter copy in the example involves a copy from const because the conversion to const is implicit.

Qualified constructors

Structs should allow defining qualified constructors and destructors:

struct Example {
  this(int);
  this(int) const;
  this(int) immutable;
  ~this();
  ~this() const;
  ~this() immutable;
}

The typechecking rules for the const constructor as as follows:

and immutable constructors are typechecked like this:

The constructor of the immutable object is typechecked with const rules because during construction the object is still changing so it cannot be considered immutable.

The assignments are considered calls to the copy constructors of the fields. Fields that are not assigned to during construction are left initialized with their .init value.

Qualified destructors are typed like ordinary qualified methods.

Aliasing

We distinguish two kinds of structs: with or without mutable aliasing. A struct has mutable aliasing if mutable data can be reached outside the struct’s storage by following fields of that struct. Example of a struct with mutable aliasing:

struct Node {
  int value;
  Node * next;
}

Mutable data is reachable starting from the field “next”. Similar examples include fields of type int[] or class type. An example of a struct without mutable aliasing:

struct Point {
  int x, y, z;
}

A less obvious example of a struct without mutable aliasing consists of structs with fields referring to immutable data. For example, the struct below also doesn’t feature mutable aliasing:

struct Widget {
  double factor;
  string name;
}

Although Widget contains aliases to data outside of itself, the reachable data is not modifiable.

The compiler statically knows for any struct type whether it has mutable aliasing or not. The behavior of qualified constructors and destructors depend on that trait as follows.

Structs without mutable aliasing

Such types don’t need to define const and/or immutable constructors. This is because it suffice to copy the bits of an object to obtain a qualified object. Consider:

struct Point {
  int x, y, z;
  this(int _x, int _y, int _z) { x = _x, y = _y, z = _z; }
}

To obtain a const Point, creating a Point and then copying its bits will suffice. Note that it’s important to copy the bits, otherwise undue aliasing may occur. Example:

int * global;
struct Point {
  int x, y, z;
  this(int _x, int _y, int _z) { x = _x, y = _y, z = _z; global = &x; }
}
...
unittest {
  auto p = immutable Point(1, 2, 3);
}

If the bits aren’t copied, then global would hold a mutable alias to an immutable value.

Copy construction of qualified objects

A struct may define

Examples

This document has been placed in the Public Domain.