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

Stanislav Blinov stanislav.blinov at gmail.com
Sun Nov 18 15:45:51 UTC 2018


On Sunday, 18 November 2018 at 15:15:11 UTC, FeepingCreature 
wrote:
> On Sunday, 18 November 2018 at 14:54:05 UTC, Stanislav Blinov 
> wrote:
>> On Sunday, 18 November 2018 at 14:47:51 UTC, FeepingCreature 
>> wrote:
>>> On Sunday, 18 November 2018 at 14:38:09 UTC, Stanislav Blinov 
>>> wrote:
>>>> @safe unittest {
>>>>     Nullable!S a; // Look ma, no assert!
>>>> }
>>>
>>> a = S(new Object); // Look pa, assert!
>>
>> That has to do with poor implementation of that example 
>> Nullable, not the union. opAssign should check for _hasValue.
>
> Right, which means you end up with moveEmplace in opAssign,

No you don't :) (I know, I know, I'm such a negative personality):

// still rudimentary, no checks for hasElaborateDestructor
struct Nullable(T) {

     this(T val) { value = val; }
     this(typeof(null) val) {}
     ~this() { cleanup(); }

     @property @trusted
     ref T value() {
         assert(_hasValue);
         return _u.value;
     }

     @property @trusted
     void value(T val) {
         // when it's rebinding, there's no need to move or 
moveEmplace,
         // just overwrite the union
         if (_hasValue) {
             // this, or it could actually assign to _u.value, 
depending on the desired semantics of Nullable
             destroy(_u.value);
             _u = U(val);
         } else {
             _u = U(val);
             _hasValue = true;
         }
     }

     @property
     void value(typeof(null)) { cleanup(); }

     void opAssign(T val) {
         // arguably this should just duplicate what `value` does,
         // to avoid unnecessary copies passed around.
         value = val;
     }
     void opAssign(typeof(null) val) { value = val; }

private:
     union U { T value = T.init; }
     U _u;
     @property ref _payload() inout { return _u.value; }
     bool _hasValue;

     void cleanup() {
         if (!_hasValue) return;
         destroy(_u.value);
         _hasValue = false;
     }
}


> which is the current Nullable implementation.

Looking at that implementation, ouch... Maybe I'm missing 
something?..

> Which only works because union{} essentially functions as a 
> semi-official backdoor in the typesystem, even in @safe. Is 
> that *really* good language design, though?

That? Yes. Unions are actually useful now, unlike what they were 
before.

Anyway, my point is that unions *are* the tool for that 
particular job, just like you said initially. Which has little to 
do with the actual topic :)
For example, I'd argue that the *actual* implementation *must* 
`move` it's passed-by-value argument (regardless of what it uses 
for storage), because the caller already made a required copy. 
But that means wiping out the argument back to T.init, and then 
we're back to square one.


More information about the Digitalmars-d mailing list