Previous Entry Share Next Entry
Explaining Language Design (Part 1: Default Initialization)
anime
he_the_great

Scott Meyers recently gave a talk, "The Last Thing D Needs," which dove into some examples of simple code which make explaining a language harder. This post series will be an attempt to recreate the examples utilizing D, giving explanations of why and to expand on what D does provide. I will not be able to follow the exact same flow as the talk, but it will be close.

This is not an attempt to dig into the dark corners of D.

The talk begins with initialization asking for the value of x1 when it is declared. I'll skip to x2.

dlang
int x2; // (at global scope)

It is important to note that D does not have a global scope. Instead this variable is being declared at module scope, and for the purposes of Scott's point this is fine. Another difference from C++ is that this variable is within Thread Local Storage, if it makes you feel better then prepending __gshared will place it in the same location as C++.

C++ will define the value of x2 to be 0 since it is essentially no runtime cost.

Similarly D also defines x2 to be 0. The reason is because variables are always initialized to their init value (T.init). In the case of integers the init value is 0 since there is no value which is invalid. It is common for a programmer to initialize an integer to -1 when desiring a sentinel value. However this is not an invalid value which will cause the program to halt; contrast -1 to the null value for pointers.

dlang
int x2prime = void// (at global scope)

D provides a way to state a variable should not be initialized. However this module variable will likely always be initialized to zero due to its low overhead.

dlang
int[100] a2; // (at global scope)

C++ and D are still in agreement that the values in a2 are 0.

dlang
void main() {
    assert(x2 == 0);
    version(noneassert(x2prime); // possibly zero
    import std.algorithm : all;
    assert(a2[].all!(x => x == 0));

Keeping with tradition, code examples are just a small piece of a larger program. Putting them together, and in order, will provide a runnable program.

The second assert has been versioned out because it is possible that it will fail if the memory happens to be 0

dlang
    static int x3;
    assert(x3 == 0);
    static int x3prime = void;
    version(noneassert(x3prime); // possibly zero

Here C++ will once again provide 0 for x3. And D will provide a means to request no initialization.

dlang
    int* px = new int;
    assert(*px == 0);

    import core.memory : GC;
    int* pxprime = cast(int*)GC.malloc(int.sizeof);
    version(noneassert(*pxprime); // possibly zero

C++ finally reaches the point where it no longer provides a guarantee on the value. The value will be a valid integer, but it will not be known exactly what. There is a good probability that the pointer will point to a memory location which is 0 resulting in a working program the majority of the time.

D guarantees that px will point to an integer with value 0. It does not provide syntax to initialize a primitive type on the heap, so it is not possible to request the location not be initialized in a straight forward manner.

dlang
    {
        int x4;
        assert(x4 == 0);
    }

C++ and D do not change behavior when a new scope is introduced, so C++ is unknown value while D guarantees 0.

dlang
    int[100] a1;
    assert(a1[].all!(x => x == 0));

    int[100] a1prime = void;

C++ avoids the runtime cost and does not specify the values for a1. D continues to guarantee initialization to 0 unless requested otherwise.

dlang
    static int[100] a3;
    assert(a3[].all!(x => x == 0));
}

The final item on initialization comes as a no runtime cost for C++ meaning a3 will contain all 0. And D continues its tradition.

Conclusion

D will always initialize variables unless explicitly requesting otherwise. The exact value will depend on the type, for example pointers will be null and floating point will be NAN. For this reason it is still important to initialize the variables in your program, however if it is forgotten the program will behave the same every run it comes across a variable with an implicit initialization.

dlang
struct Point {
    int x;
    int y;

    @disable this();
}

void main() {
    Point p;
}

What is the value of p?

D provides the ability to disable the default constructor. This is a way to force the user into providing specific initialization. What this means is that the compiler will fail preventing p from ever existing. One of the primary goals of this feature is to provide a NotNull template wrapper, at the time of this writing the NotNull wrapper has not had all the details flushed out.

  1. Part 1: Default Initialization
  2. Part 2: Const Inference
  3. Part 3: Lambdas in C++
  4. Part 4: Lambdas in D
  5. Part 5: Type Inference
  6. Part 6: Inheritance
  7. Part 7: Algorithm Complexity
  8. Part 8: Essential Complexity

You are viewing he_the_great