November 18, 2018
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!
November 18, 2018
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.
November 18, 2018
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!

...i.e. it should be:

// ...

    @property @trusted
    void value(T val) {
        if (!_hasValue) {
            import std.conv : emplace;
            auto addr = () @trusted { return &_payload(); } ();
            emplace(addr, val);
            _hasValue = true;
        } else
            _payload = val;
    }


// ...

since otherwise that calls opAssign. Unless you'd propose to not call invariants for that one too :*)
November 18, 2018
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, which is the current Nullable implementation. Which only works because union{} essentially functions as a semi-official backdoor in the typesystem, even in @safe. https://run.dlang.io/is/YAnVBV

Is that *really* good language design, though?
November 18, 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.
November 18, 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.

November 19, 2018
On Sunday, 18 November 2018 at 19:33:44 UTC, FeepingCreature wrote:
> On Sunday, 18 November 2018 at 15:45:51 UTC, Stanislav Blinov wrote:

>> 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.

You misunderstood what I said, and as you saw in my last example you don't need a moveEmplace for the current implementation. What I'm saying is, the actual implementation *should* be doing this:

void opAssign(T rhs) {
    // ...
    move(rhs, myOwnStorage);
    // ...
}

T, for all I know, could be non-copyable, i.e. a Unique. That way the onus of making an instance falls solely on the caller, while Nullalbe would never call or deal with any copy constructors. Recall that D "frowns upon" self-referencing types. Although the language can't statically disallow them, it's free to assume they don't exist. Therefore, moving values around without calling their copy ctors should be acceptable.
That's not a `union` problem. If you do it like this where T is your S, you'll get an assert due to invariant. I.e. that is the subject problem :)

> ...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.

Again, that is not a `union` problem, that's a destructor+invariant problem. Types in .init state should be destructible, period:

S[] a;
// can't do this:
a = new S[10];
// but still can do this:
a.length = 10;
November 19, 2018
On Monday, 19 November 2018 at 01:46:34 UTC, Stanislav Blinov wrote:
> Again, that is not a `union` problem, that's a destructor+invariant problem. Types in .init state should be destructible, period:
>
> S[] a;
> // can't do this:
> a = new S[10];
> // but still can do this:
> a.length = 10;

Fair enough, I agree with that, it's just been a bit of an uphill struggle to get people to agree that requiring T.init to pass the invariants makes struct invariants mostly useless. If you can get them to agree to a solution that doesn't nerf struct invariants into the ground, then be my guest - there's a DMD PR that could be resurrected, https://github.com/dlang/dmd/pull/8462 , or a better solution found. I just really don't want to have to take our codebase back to classes for domain values, or comment out all the invariants we painstakingly added.
November 25, 2018
Ping.
November 25, 2018
On Sunday, 25 November 2018 at 16:05:11 UTC, FeepingCreature wrote:
> Ping.

Pong on GH.
1 2
Next ›   Last »