1 static assert(defaultInit!int == 0); 2 static assert(defaultInit!bool == false); 3 static assert(defaultInit!(int*) is null); 4 static assert(defaultInit!(int[]) is null); 5 static assert(defaultInit!string is null); 6 static assert(defaultInit!(int[2]) == [0, 0]); 7 8 static struct S 9 { 10 int i = 42; 11 } 12 static assert(defaultInit!S == S(42)); 13 14 static assert(defaultInit!Object is null); 15 16 interface I 17 { 18 bool foo(); 19 } 20 static assert(defaultInit!I is null); 21 22 static struct NoDefaultInit 23 { 24 int i; 25 @disable this(); 26 } 27 28 // It's not legal to default-initialize NoDefaultInit, because it has 29 // disabled default initialization. 30 static assert(!__traits(compiles, defaultInit!NoDefaultInit)); 31 32 int var = 2; 33 struct Nested 34 { 35 int i = 40; 36 37 int foo() 38 { 39 return i + var; 40 } 41 } 42 43 // defaultInit doesn't have access to this scope and thus cannot 44 // initialize the nested struct. 45 static assert(!__traits(compiles, defaultInit!Nested)); 46 47 // However, because Nested has no static opCall (and we know it doesn't 48 // because we're doing this in the same scope where Nested was declared), 49 // Nested() can be used to get the default-initialized value. 50 static assert(Nested() == Nested(40)); 51 52 Nested nested; 53 assert(Nested() == nested); 54 55 // Both have properly initialized context pointers, 56 // whereas Nested.init does not. 57 assert(Nested().foo() == nested.foo()); 58 59 // defaultInit does not get hijacked by static opCall. 60 static struct HasOpCall 61 { 62 int i; 63 64 static opCall() 65 { 66 return HasOpCall(42); 67 } 68 69 static opCall(int i) 70 { 71 HasOpCall retval; 72 retval.i = i; 73 return retval; 74 } 75 } 76 77 static assert(defaultInit!HasOpCall == HasOpCall(0)); 78 static assert(HasOpCall() == HasOpCall(42));
Evaluates to the default-initialized value of the given type.
defaultInit should be used in generic code in contexts where the default-initialized value of a type is needed rather than that type's init value.
For most types, the default-initialized value of a type is its init value - i.e. for some type, T, it would normally be T.init. However, there are some corner cases in the language where a type's init value is not its default-initialized value. In particular,
1. If a type is a non-$(K_STATIC) nested struct, it has a context pointer which refers to the scope in which it's declared. So, its default-initialized value is its init value plus the value for its context pointer. However, if such a nested struct is explicitly initialized with just its init value instead of being default-initialized or being constructed via a constructor, then its context pointer is $(K_NULL), potentially leading to segfaults when the object is used. 2. If a type is a struct for which default initialization has been disabled using @disable this();, then while its init value is still used as the value of the object at the start of a constructor call (as is the case with any struct), the struct cannot be default-initialized and must instead be explicitly constructed. So, instead of T.init being the default-initialized value, it's just the struct's initial state before the constructor constructs the object, and the struct does not actually have a default-initialized value.
In the case of #2, there is no default initialization for the struct, whereas in the case of #1, there is default initialization for the struct but only within the scope where the struct is declared. Outside of that scope, the compiler does not have access to that scope and therefore cannot iniitialize the context pointer with a value, so a compilation error results. And so, either case can make it so that a struct cannot be default-initialized.
In both cases, an instance of the struct can still be explicitly initialized with its init value, but that will usually lead to incorrect code, because either the object's context pointer will be $(K_NULL), or it's a type which was designed with the idea that it would only ever be explicitly constructed. So, while sometimes it's still appropriate to explicitly use the init value (e.g. by default, $(REF1 destroy, object) will set the object to its init value after destroying it so that it's in a valid state to have its destructor called afterwards in cases where the object is still going to be destroyed when it leaves scope), in general, generic code which needs to explicitly default-initialize a variable shouldn't use the type's init value.
For a type, T, which is a struct which does not declare a $(K_STATIC) opCall, T() can be used instead of T.init to get the type's default-initialized value, and unlike T.init, it will fail to compile if the object cannot actually be default-initialized (be it because it has disabled default initialization, or because it's a nested struct outside of the scope where that struct was declared). So, unlike T.init, it won't compile in cases where default initialization does not work, and thus it can't accidentally be used to initialize a struct which cannot be default-intiialized. Also, for nested structs, T() will initialize the context pointer, unlike T.init. So, using T() normally gives the actual default-initialized value for the type or fails to compile if the type cannot be default-initialized.
However, unfortunately, using T() does not work in generic code, because it is legal for a struct to declare a $(K_STATIC) opCall which takes no arguments, overriding the normal behavior of T() and making it so that it's no longer the default-initialized value. Instead, it's whatever $(K_STATIC) opCall returns, and $(K_STATIC) opCall can return any type, not just T, because even though it looks like a constructor call, it's not actually a constructor call, and it can return whatever the programmer felt like - including $(K_VOID). This means that the only way to consistently get a default-initialized value for a type in generic code is to actually declare a variable and not give it a value.
So, in order to work around that, defaultInit does that for you. defaultInit!Foo evaluates to the default-initialized value of Foo, but unlike Foo.init, it won't compile when Foo cannot be default-initialized. And it won't get hijacked by $(K_STATIC) opCall.
The downside to using such a helper template is that it will not work with a nested struct even within the scope where that nested struct is declared (since defaultInit is declared outside of that scope). So, code which needs to get a default-initialized instance of a nested struct within the scope where it's declared will either have to simply declare a variable and not initialize it or use T() to explicitly get the default-initialized value. And since this is within the code where the type is declared, it's fully within the programmer's control to not declare a $(K_STATIC) opCall for it. So, it shouldn't be a problem in practice.