T.init, struct destructors and invariants - should they be called?

FeepingCreature feepingcreature at gmail.com
Fri Jul 6 10:44:09 UTC 2018


I believe there's a good case that struct invariants should not 
be called on struct destruction. Significantly, I believe that 
Phobos, in particular `moveEmplace`, is already written as if 
this is the case, even though it is not specified anywhere.

It is very common for structs' .init to violate invariants. 
Consider the humble struct S
{
     Object obj;
     invariant
     {
         assert(this.obj !is null);
     }
     @disable this();
     this(Object obj)
     in(obj !is null)
     {
         this.obj = obj;
     }
}

S is obviously intended to be constructed with a nonzero 
this.obj. However, even in @safe code we may take S.init, which 
violates the invariant.

Consequently, this code currently asserts out:

     S s = S.init;

Why is this a problem? ("Just don't use S.init!")

Well, for one it makes Nullable!S impossible. Nullable, if it is 
to be @nogc, *necessarily* has to construct an S.init struct 
member. This leads us into trouble:

     Nullable!s ns = Nullable!s();

^ Asserts out again - there is no way for Nullable to avoid 
invoking the destructor of its internal S field.

Now, currently this is not a problem because Nullable refuses to 
compile in this case. However, such a basic language feature 
should *really* work for every type, especially given that such 
basic types as SysTime already set up Nullable's reasonable fears 
of invalid behavior.

There is a simple tweak to make this work:

Simply require that struct constructors be defined for T.init, 
even if T.init should violate invariants. This can be implemented 
by not checking the invariant if `this is T.init`, or simply 
disabling the invariant in the struct destructor entirely.

I picked the second option, but more because I didn't know how to 
do the first.

https://github.com/dlang/dlang.org/pull/2410

https://github.com/dlang/dmd/pull/8462

About `moveEmplace`: If `moveEmplace` detects that the struct 
type it is operating on has a user-defined destructor, it 
manually overwrites its source with `T.init`. This suggests that 
`moveEmplace` already operates on this logic. Hence: it's 
necessary, it's unavoidable, and it has precedent.

Opine?


More information about the Digitalmars-d mailing list