Defaut initialization of structs
Jonathan M Davis
newsgroup.d at jmdavisprog.com
Mon Oct 13 14:13:26 UTC 2025
On Monday, October 13, 2025 4:44:58 AM Mountain Daylight Time Per Nordlöw via Digitalmars-d-learn wrote:
> Given a `struct S` is, `S()` always equivalent to `S.init`? I
> wonder which is preferred in an (parameter default value)
> expression that should evaluate to the default value of `S`.
>
> I would like to use `S()` as its shorter to type than `S.init`
> but I'm uncertain whether it can trigger execution of code
> despite D disallows nullary constructors.
Originally, there wasn't supposed to be any difference between these
S s;
auto s = S.init;
auto s = S();
but changes to the language over time have made it so that they're all
subtly different. Strictly speaking, S.init is the value used to
default-initialize an object, but it's not necessarily the
default-initialized value.
The main stuff that causes differences are:
1. Structs which disable default initialization.
2. Non-static nested structs.
3. Structs which define static opCall.
If a struct disables default initialization, then
auto s = S.init;
is legal, but
S s;
auto s = S();
aren't. So, in that particular situation, you almost certainly don't want to
use S.init.
If a struct is a non-static nested struct, then
S s;
auto s = S();
will both properly initialize the struct. However,
auto s = S.init;
will properly initialize all of the struct _except_ for the context pointer,
which will be null. So, if the struct ever actually uses its outer context,
it will segfault. So, in that situation, you almost certainly don't want to
use S.init.
And if a struct defines a static opCall which can be called with no
arguments, then
S s;
auto s = S.init;
will properly initialize the struct, whereas
auto s = S();
will do whatever the static opCall does (and there's no guarantee that the
static opCall even returns a value let alone that it returns an S). So, in
that situation, you almost certainly don't want to be using S() (and if you
do, it's because you know exactly what type you're dealing with and how it
will behave).
So, some folks have taken to using S() rather than S.init, because it
doesn't bypass the restrictions when disabling default initialization, and
it fully initializes non-static nested structs, but in generic code at
least, it's still potentially going to do the wrong thing, because a struct
could define static opCall.
This means that in the general case (and particularly with generic code),
code should use neither S.init nor S(). Rather, it should let a variable
default-initialize itself and use that. Using either S.init or S() will
result in the code doing the wrong thing at least some of the time. There
are still specific cases where you might want to use one or the other, but
normal code really shouldn't be using either unless you know exactly how the
types you're dealing with will behave - which of course means that generic
code shouldn't use either S.init or S() unless it has additional checks to
make sure that the type doesn't disable default initialization and doesn't
define static opCall.
This of course is kind of annoying in cases where you need to pass a
default-initialized value but don't need a variable - e.g. foo(S.init) - but
as far as language buit-ins go, to do the correct thing in the general case,
you need to declare a variable and use it if you want the code to be correct
regardless of the type being used. To work around this, Phobos v3 has a
helper trait to solve the problem.
template defaultInit(T)
if (is(typeof({T t;})))
{
enum defaultInit = (){ T retval; return retval; }();
}
So, then you'd do something like foo(defaultInit!S) instead. It doesn't
actually work with non-static nested structs, because they can only be
constructed in the scope where they're declared, but in that situation,
you're dealing with a specific type that you control and will know that
static opCall wasn't declared.
However, since defaultInit is in Phobos v3 (which is nowhere near ready),
it's obviously not something that I can tell you to go use instead (outside
of copying it to your own code).
So, if you're writing generic code (particularly if it's in a pubicly
available library), you're going to want to either just declare the variable
and use it (so that it's default-initialized normally), or you're going to
want to create a helper trait like defaultInit and use that. You do not want
to be using either S.init or S() outside of some fairly specific
circumstances.
However, if you're writing non-generic code, and you know what the type in
question is going to do, then both S.init and S() should be fine. In the
vast majority of cases, there isn't actually a difference. It's just that
there are corner cases where there is a difference, and that's potentially
going to screw up generic code.
- Jonathan M Davis
More information about the Digitalmars-d-learn
mailing list