D is dead

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sat Aug 25 14:37:16 UTC 2018


On Saturday, August 25, 2018 7:33:47 AM MDT Shachar Shemesh via Digitalmars-
d wrote:
> On 25/08/18 10:56, Walter Bright wrote:
> > On 8/24/2018 6:34 AM, Shachar Shemesh wrote:
> >> No, unlike what I suggest, that doesn't work without carefully
> >> reviewing every single place you put it to see whether the constructor
> >> actually supports destructing a partially constructed object.
> >
> > All D objects are default-initialized before the constructor sees it
> > (unlike C++). A destructor should be able to handle a
> > default-initialized object.
>
> I'm not talking about a default initialized object. I'm talking about an
> object where the constructor started running, but not to completion.
>
> With that said, this statement is, I think, representative of the Lego
> problem D has. All D objects? Really? Even this one?
>
> struct A {
>    int a;
>
>    @disable this();
>    @disable init;
>
>    this(int number);
>    ~this();
> }
>
> If you allow a feature to be disabled, you really need to keep in mind
> that feature might be well and truly disabled.

As I understand it, it's not actually possible to disable the init value.
Even if bodies are provide for the constructor and destructor (so that they
don't give you errors), you just end up with an error like

q.d(14): Error: no identifier for declarator init

You could replace that with something like

    @disable void init();

but that's only legal because declaring a member named init has never been
made illegal even though it's generally been agreed upon (by most of the
core devs anyway) that it should be. The way D is designed, there _must_ be
an init value. The closest that you can get to disabling it is the

    @disable this();

line which just disables default initialiation. The init value still gets
used when constructing the object, and it can still be used explicitly.

If void initialization of member variables worked the way that some folks
think that it should - including Andrei and Walter:

https://issues.dlang.org/show_bug.cgi?id=11331
https://issues.dlang.org/show_bug.cgi?id=11817

then I think that that would definitely be an example that would fit the
point that you're trying to make (since they you have to worry about the
constructor partially setting values on top of garbage and then trying to
destroy that correctly), but at least for the moment, it doesn't actually
work. Having to worry about destructors running on void initialized objects
is a similar problem, but not quite the same, since that doesn't involve an
exception being thrown from a constructor. Regardless, even if a type is
designed such that its init value can be properly destroyed, and you don't
have to worry about void initialization, it can't be all that hard to design
it such that the destructor won't work properly if the constructor doesn't
complete properly.

What all of this makes me think of though is a similar problem that
FeepingCreature loves to bring up and complain about, which is that
invariants that consider the init value to be invalid (e.g. if the invariant
checks that a member variable is non-null) blow up in your face if the type
has a destructor, because the invariant gets run before the destructor, and
member variables that are pointers will of course be null in the init value.
And while disabling default initialization helps, it doesn't fix the problem
because of code that explicitly uses the init value. To work around this,
he's come up with some weird solution using unions that's probably going to
break on him at some point (though if I understand correctly, he's changed
std.typecons.Nullable to use it, which makes it a lot less likely that it's
going to break). But really, having an invariant that fails for the init
value is generally a recipe for disaster, much as it's easy to see why it
would be desirable for stuff like pointers.

What's worse is that once void initialization is involved, an invariant is
almost certainly going to fail, because the invariant gets called before
opAssign. And that's the number one reason that I never use invariants in
structs anymore.

In any case, all of that is really a tangent with regards to init values,
but it's definitely a sign of how some of the stuff around init values,
constructors, and destructors doesn't really play well together. And sadly,
it's really not that hard to get into a state where your destructor is going
to have serious problems if it's run. In general, any place where D was
designed around the idea that something would _always_ be there (e.g. init
values and default initialization) but we've then later added the ability to
get around it (e.g. void initialization or @disable) has tended to not play
well with everything else and has caused some fun problems.

- Jonathan M Davis





More information about the Digitalmars-d mailing list