Preventing .init for Archive struct, Programming in D, page 295

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sun Sep 14 02:09:21 UTC 2025


On Friday, September 12, 2025 9:20:38 AM Mountain Daylight Time Brother Bill via Digitalmars-d-learn wrote:
> Is is possible to 'disable' .init for a struct?

Technically, yes, but you should absolutely never do it, and it will likely
become illegal to do so at some point in the future.

init is a core part of the language, and all types have it. The fact that
the ability to disable default initialization was added was arguably a
mistake. There _are_ cases where it can be quite useful, so whether it
should be allowed is definitely debatable, but the fact that it's a thing
has definitely complicated the language. It's particularly problematic for
generic code.

And even when default initialization is disabled, there are still cases
where the init value is required (even if you the average programmer should
not be writing such code).

For instance, the init value is what's used by the compiler (and in some
cases, the runtime) when initializing a variable of that type prior to
calling its constructor. Even when a struct has disabled default
initialization, that's still the case.

Unfortunately, it _is_ still legal to declare your own init member for a
type, but doing that just breaks things, and you should never do it. And
because it's possible to declare an init member, it's possible to declare an
init member with @disable, but that will ultimately just break code. Don't
do it.

At some point, it probably will become illegal to declare a member called
init precisely because it breaks things. The only reason that it's legal is
that Walter orignally had thought that it would be a good idea to make it
possible to define your own init value to override the default behavior, but
in practice, things do not work that way, and you should never attempt it if
you don't want broken code. D code in general (including in the runtime)
assumes that T.init is the init value as defined by the compiler and that it
has not been disabled in any fashion. And your code should not attempt to
break that assumption.

That being said, normal code should not be using an explicit init value with
any type which has disabled default initialization, because it makes it far
too easy to accidentally bypass that restriction.

And really, in general, you just shouldn't be using the init value of a type
explicitly, and the fact that default initialization can be disabled is a
big part of that. A related issue is the fact that non-static nested structs
have a context pointer, and while it will be properly initialized when the
struct is default-initialized, it will be initialized to null if you
explicitly initialize a struct with its init value (because the value for
the context pointer in the init value is null).

So, in general, if possible, you should let structs be default-initalized
rather than trying to explicitly give them their default value, but in cases
where you do need to explicitly pass the default value - e.g. foo(T.init) -
it's still better to not use the init value.

One workaround that's sometimes used is to use T() instead of T.init, so
you'd do something like foo(T()) instead. This works in _most_ situations
and is very much preferable to using T.init, because T() will normally give
you the default-initialized value for T (including initializing the context
pointer if T is a non-static nested struct), and it won't compile if default
initialization has been disabled for T. So, you won't accidentally use the
init value as the default-initialized value of a type that doesn't have
default initialization.

However, that falls apart when static opCall is involved. If a static opCall
which takes no arguments has been declared for T, then that's what T() will
use instead of giving the default-initialized value. This means that in
normal generic code, you can't use T.init, and you can't use T(). Rather,
you need to do something like declare a variable without giving it an
explicit value and then use that variable. Phobos v3 will have a helper
template for that (and maybe it should be merged into Phobos v2), which is
basically

template defaultInit(T)
if (is(typeof({T t;})))
{
    enum defaultInit = (){ T retval; return retval; }();
}

so that you can then use defaultInit!Foo instead of Foo.init or Foo(), but
whether such a helper template is used or not, ultimately, most generic code
should not be using T() or T.init, because it will not work correctly with
all types.

So, in your own code, you shouldn't normally use T.init or T(), and you
shouldn't disable default initialization unless it's absolutely necessary,
since the type will then not compile with some language features (e.g.
dynamic arrays), because they require default initialization to work, and
there is likely to be some generic code which used T.init when it shouldn't
have, because it didn't used to be possible to disable default
initialization (or because the programmer who wrote it forgot to take
disable default initialization into account). So, if you're passing a type
which has disabled default initialization to third party code, there's a
real chance that it'll end up being initialized with its init value at some
point anyway, because 99.99+% of types don't have that problem, and unless
someone thought to test for it, it probably wasn't tested properly with a
particular piece of code.

And trying to disable the init value will make things even worse, because
there is legitimately code that needs to use the init value to do its job
even if default initialization is disabled for a type. Similarly, if you
declare a type which disables default initialization and which has a
destructor, the destructor needs to work when the object is its init value,
since there are situations where that's going to happen. That doesn't mean
that the type needs to have all of its functionality work when it's its init
value, since it shouldn't be being set to its init value under normal
circumstances, but there are still going to be situtations where it can
happen, and the destructor will be run after that (e.g. if an object is
moved, the variable that's left behind will be set to the type's init value
so that that can be destroyed instead of the object that was moved).

So, all in all, I'd advise against trying to disable default initialization
for a struct unless you really know what you're doing. And if you have a
type that really can't work properly with default initialization, you might
want to consider using a class instead of a struct, since you won't get any
class objects separate from a reference, and the init value for the
reference is null. So, any class object that you use is going to have been
constructed with one of its constructors, and you won't have to worry about
default-initialized class objects.

- Jonathan M Davis






More information about the Digitalmars-d-learn mailing list