March 12, 2021
On Thursday, 11 March 2021 at 21:41:59 UTC, Walter Bright wrote:
> Why? There can be no other uses of the rvalue, so why not simply move it?

If you have two objects in your move constructor, but only one at the end, then a destruction must take place somewhere.
March 12, 2021
On Thursday, 11 March 2021 at 22:39:21 UTC, Walter Bright wrote:
> All the problems with const/immutable revolved around postblit. That's why this proposal has zero reliance on postblit.

These problems seems to arise due to the fact postblit did not distinguish between move and copy.

postblit is inappropriate for copy, but as far as I can tell, not only does it work for move, but it's the only path that do not involve creating yet more magic that's going to bite us in the ass at some point.
March 12, 2021
On Thursday, 11 March 2021 at 21:45:49 UTC, tsbockman wrote:
> On Thursday, 11 March 2021 at 08:36:18 UTC, Walter Bright wrote:
>> On 3/10/2021 8:17 PM, tsbockman wrote:
>>> Wouldn't it make more sense to just skip the move operation entirely when the source and destination are the same? Or are there circumstances in which that cannot be determined, even at runtime?
>>
>> The idea is that the move assignment operation takes care of this, and makes it "as if" the move was done, then the destruction.
>
> My concern is how to make code like this work correctly:
> ...
> // Maybe this is your proposed lowering for the `a = b;` line in main, above?
> {
>     S oldDest = a; // You all say no copy constructor call is necessary here, but...
>     a.opAssign(b); // (Body is the same as that of the move constructor.)
>     // ...the implicit destroy(oldDest); at scope exit crashes if oldDest was only blit.
> }
>
> You can put the lowering logic inside of the move opAssign instead, but it doesn't change the fundamental problem. If no actual general-purpose lowering is possible that accurately reflects the intended semantics of, "After the move is complete, the destructor is called on the original contents of the constructed object," then the DIP's description of the semantics is simply incorrect and should be replaced with something more rigorous.

I think I finally figure out how to make some sense out of the DIP's description. However, the lowering cannot be expressed clearly with the DIP's syntax, so I will use an alternative notation:

void moveConstruct(ref S source) nothrow @nogc {
    if(source.isUnique) {
        ptr = &internal;
        internal = source.internal;
    } else
        ptr = source.ptr;
}
void moveAssign(ref S source) @trusted nothrow @nogc {
    S oldDest = void;
    oldDest.moveConstruct(this); // Move the old value to a temporary.
    moveConstruct(source);
    // Implicitly destroy the old value.
}

Is this correct? If so, the DIP really needs to explain it more clearly - especially if the user is expected to implement some equivalent in the custom move operator himself, rather than the compiler doing it for him.
March 12, 2021
On Friday, 12 March 2021 at 01:40:22 UTC, tsbockman wrote:
> On Thursday, 11 March 2021 at 21:45:49 UTC, tsbockman wrote:
>> You can put the lowering logic inside of the move opAssign instead, but it doesn't change the fundamental problem. If no actual general-purpose lowering is possible that accurately reflects the intended semantics of, "After the move is complete, the destructor is called on the original contents of the constructed object," then the DIP's description of the semantics is simply incorrect and should be replaced with something more rigorous.
>
> I think I finally figure out how to make some sense out of the DIP's description. However, the lowering cannot be expressed clearly with the DIP's syntax, so I will use an alternative notation:
>
> void moveConstruct(ref S source) nothrow @nogc {
>     if(source.isUnique) {
>         ptr = &internal;
>         internal = source.internal;
>     } else
>         ptr = source.ptr;
> }
> void moveAssign(ref S source) @trusted nothrow @nogc {
>     S oldDest = void;
>     oldDest.moveConstruct(this); // Move the old value to a temporary.
>     moveConstruct(source);
>     // Implicitly destroy the old value.
> }
>
> Is this correct? If so, the DIP really needs to explain it more clearly - especially if the user is expected to implement some equivalent in the custom move operator himself, rather than the compiler doing it for him.

Updated runnable example, for context:
    https://gist.github.com/run-dlang/c5805ebb9e9b9734e032ca5e81fcfa90

This version seems to work correctly with either lowering enabled.
March 11, 2021
On 3/11/2021 4:57 PM, deadalnix wrote:
> These problems seems to arise due to the fact postblit did not distinguish between move and copy.
> 
> postblit is inappropriate for copy, but as far as I can tell, not only does it work for move, but it's the only path that do not involve creating yet more magic that's going to bite us in the ass at some point.

postblit bit us in the ass quite a bit. (postblit didn't do moves)
March 12, 2021
On Friday, 12 March 2021 at 01:43:01 UTC, tsbockman wrote:
> On Friday, 12 March 2021 at 01:40:22 UTC, tsbockman wrote:
>> I think I finally figure out how to make some sense out of the DIP's description. However, the lowering cannot be expressed clearly with the DIP's syntax, so I will use an alternative notation:
>>
>> void moveConstruct(ref S source) nothrow @nogc {
>>     if(source.isUnique) {
>>         ptr = &internal;
>>         internal = source.internal;
>>     } else
>>         ptr = source.ptr;
>> }
>> void moveAssign(ref S source) @trusted nothrow @nogc {
>>     S oldDest = void;
>>     oldDest.moveConstruct(this); // Move the old value to a temporary.
>>     moveConstruct(source);
>>     // Implicitly destroy the old value.
>> }
>>
>> Is this correct?

Nope, still not correct. I had a bug in the surrounding context, and wasn't testing enough things. Here's another attempt that passes a more thorough test:
    https://gist.github.com/run-dlang/b789714c01905f091a44ee2666276433

The important bit:

/* move construction and assignment (these must be called manually
and do not use the DIP syntax, since it's not implemented yet): */
void moveConstruct(ref S source) @system nothrow @nogc {
    // @system since this must not be called by itself on an already-initialized object.
    if(source.isUnique) {
        ptr = &internal;
        internal = source.internal;
    } else
        ptr = source.ptr;
    source.ptr = null;
}
void moveAssign(ref S source) @trusted nothrow @nogc {
    static if(useDIPLowering) {
        // destroy after (the DIP's proposal):
        S newVal = void;
        newVal.moveConstruct(source);
        S oldVal = void;
        oldVal.moveConstruct(this);
        moveConstruct(newVal);
        // Implicitly destruct(oldVal).
    } else {
        // conditionally move and destroy before (my proposal):
        if(&source !is &this) {
            destruct(this);
            moveConstruct(source);
        }
    }
}

Key changes: the move constructor must put the source into a state where the destructor is a no-op, and for the move assignment operation to destroy the old value *after* the move, as required by the DIP, TWO extra moves are required. That is a lot of extra work and confusion just to avoid explicitly checking if the source and destination are the same. This seems especially silly given that the optimizer can probably detect the move-to-self case at compile time in many cases, and eliminate either the test, or the entire move during compilation.

Is there some other motivation for destroying after, rather than before, besides the self-move case?
March 12, 2021
On 3/11/2021 4:53 PM, deadalnix wrote:
> On Thursday, 11 March 2021 at 21:41:59 UTC, Walter Bright wrote:
>> Why? There can be no other uses of the rvalue, so why not simply move it?
> 
> If you have two objects in your move constructor, but only one at the end, then a destruction must take place somewhere.

The whole point of move construction is to move an initialized object to an unconstructed object. No destruction is needed.

It's move assignment that needs to destruct something (the original value of the destination).
March 12, 2021
On Friday, 12 March 2021 at 06:52:09 UTC, tsbockman wrote:
> On Friday, 12 March 2021 at 01:43:01 UTC, tsbockman wrote:
>> On Friday, 12 March 2021 at 01:40:22 UTC, tsbockman wrote:
>>> [...]
>
> Nope, still not correct. I had a bug in the surrounding context, and wasn't testing enough things. Here's another attempt that passes a more thorough test:
>     https://gist.github.com/run-dlang/b789714c01905f091a44ee2666276433
>
> The important bit:
>
> /* move construction and assignment (these must be called manually
> and do not use the DIP syntax, since it's not implemented yet): */
> void moveConstruct(ref S source) @system nothrow @nogc {
>     // @system since this must not be called by itself on an already-initialized object.
>     if(source.isUnique) {
>         ptr = &internal;
>         internal = source.internal;
>     } else
>         ptr = source.ptr;
>     source.ptr = null;
> }
> void moveAssign(ref S source) @trusted nothrow @nogc {
>     static if(useDIPLowering) {
>         // destroy after (the DIP's proposal):
>         S newVal = void;
>         newVal.moveConstruct(source);
>         S oldVal = void;
>         oldVal.moveConstruct(this);
>         moveConstruct(newVal);
>         // Implicitly destruct(oldVal).
>     } else {
>         // conditionally move and destroy before (my proposal):
>         if(&source !is &this) {
>             destruct(this);
>             moveConstruct(source);
>         }
>     }
> }
>
> Key changes: the move constructor must put the source into a state where the destructor is a no-op, and for the move assignment operation to destroy the old value *after* the move, as required by the DIP, TWO extra moves are required. That is a lot of extra work and confusion just to avoid explicitly checking if the source and destination are the same. This seems especially silly given that the optimizer can probably detect the move-to-self case at compile time in many cases, and eliminate either the test, or the entire move during compilation.
>
> Is there some other motivation for destroying after, rather than before, besides the self-move case?

Not trying to be rude, but it's a bit worrying that even those who know D really well have a hard time understanding the DIP... (I don't count myself as one of them yet)

Maybe it should provide even more "end-to-end" code examples to show how all parts work?

Maybe we're over-analyzing things, idk, but I guess we want to be sure everything is 100% correct and can be explained (relatively easy) to a certain percentage of the community.

The discussion implies there are parts not fully understood and that makes people nervous.
March 12, 2021
On Friday, 12 March 2021 at 11:35:03 UTC, Imperatorn wrote:
> On Friday, 12 March 2021 at 06:52:09 UTC, tsbockman wrote:
>> [...]
>
> Not trying to be rude, but it's a bit worrying that even those who know D really well have a hard time understanding the DIP... (I don't count myself as one of them yet)
>
> Maybe it should provide even more "end-to-end" code examples to show how all parts work?
>
> Maybe we're over-analyzing things, idk, but I guess we want to be sure everything is 100% correct and can be explained (relatively easy) to a certain percentage of the community.
>
> The discussion implies there are parts not fully understood and that makes people nervous.

The DIP will be fleshed out more. However I should stress that the DIP should be viewed as a piece of legalese rather than a sales pitch as per se - i.e. this isn't the place to be talking about pedagogy
March 12, 2021
On Friday, 12 March 2021 at 11:47:49 UTC, Max Haughton wrote:
> [snip]
>
> The DIP will be fleshed out more. However I should stress that the DIP should be viewed as a piece of legalese rather than a sales pitch as per se - i.e. this isn't the place to be talking about pedagogy

I think Herb Sutter's C++ proposals do a good job of doing both. For instance, the metaclasses proposal [1] contains high level explanations and simple examples, but also gets into more detail (maybe less of the legalese in that particular one though). I think the legalese is important, but communicating why the idea is important and how it should be used (and useful) in simple cases is not something that should be discounted as just a "sales pitch" or "pedagogy", IMO.

[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0707r0.pdf