T.init, struct destructors and invariants - should they be called?
FeepingCreature
feepingcreature at gmail.de
Sun Nov 18 19:33:44 UTC 2018
On Sunday, 18 November 2018 at 15:45:51 UTC, Stanislav Blinov
wrote:
> 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;
> }
This will not work if your type has an immutable field, by the by.
> 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?..
>
> 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.
Right, which is why we make a *second* copy, store it in a Union,
and moveEmplace that.
>> 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.
>
Yeah they're useful in that they're helping us to get the
language to defeat itself.
A destructor is something that is run when an expression goes out
of scope. Except if that expression is stored in a union, because
a union is a Destructor Blocker™.
No it's not! A union is a way to make multiple expressions occupy
the same area of memory. Its definition has *nothing* to do with
destructor blocking. The only reason that unions are destructor
blockers are that they *can't* logically support destructors, and
for some ~magical reason~ dlang has decided that it's @safe™ to
let us store values with destructors in union fields anyway,
basically for no reason other than that we decided that @safe was
*too* @safe and we wanted it to be less @safe because if it was
as @safe as it claimed it was, it'd be inconvenient. But to me,
that indicates that @safe is broken, and I really don't believe
in breaking an unrelated feature in order that I can
coincidentally unbreak the original brokenness manually. And no,
just because I put a fancy term on the brokenness doesn't make it
any less broken. Unions let me take a @safe expression whose copy
constructor has ran and avoid calling its destructor. This is
useful because @safe would otherwise require me to run a
destructor on some expressions whose constructor has never run.
But two wrong designs don't make a correct design. Just because
the building is on fire doesn't validate the decision to leave a
giant jagged hole in the front wall.
More information about the Digitalmars-d
mailing list