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