November 27, 2018
On Tuesday, 27 November 2018 at 12:14:17 UTC, Jonathan M Davis wrote:

> Well, if the compiler were doing the move...

[wall snipped]

> And simply flagging common functions that do moves isn't going to be enough to catch all cases - especially when all it takes to hide the fact that a move occurred is to wrap the move call in another function that the compiler doesn't have visibility into thanks to separate compilation.

"Easy" enough. The move and emplace families should be compiler intrinsics, not library functions.

November 27, 2018
On Tuesday, 27 November 2018 at 12:03:20 UTC, Sebastiaan Koppe wrote:
> On Tuesday, 27 November 2018 at 10:59:03 UTC, Alex wrote:
>> There exist
>>
>> auto val = Handle.init;
>>
>> 1. How do you treat this?
> I have no idea. Logically I would say that - in my case - the val is invalid.
>

This is a source of bugs. The T.init value is the one and only value, which exists, independently from the definition (of Handle). Therefore, your own functions on Handle have to consider this case and have to behave properly. I.e. define some behavior.

I mean, what would happen, if you take an existent function and pass Handle.init to it? Would it assert? Would it yield 42? Some reproducible behavior is defined well enough.

>> 2. Why do you don't want to treat the handle after movement the same way?
> Because the handle refers to an underlying resource, and any access to that resource through that handle is invalid after a move. Much like one doesn't want to call .release() twice on an unique(T) wrapper type.
>
> Sure, I could put in a runtime check, but then I get runtime errors and I rather have compile time errors.

This is not an answer to my question. The question was: why you would like to have a different behavior w.r.t. some other behavior, which exists, independent of your actions.
November 27, 2018
On Tuesday, 27 November 2018 at 12:42:43 UTC, Alex wrote:
> On Tuesday, 27 November 2018 at 12:03:20 UTC, Sebastiaan Koppe wrote:

>>> auto val = Handle.init;
>>>
>>> 1. How do you treat this?
>> I have no idea. Logically I would say that - in my case - the val is invalid.
>>
>
> This is a source of bugs. The T.init value is the one and only value, which exists, independently from the definition (of Handle). Therefore, your own functions on Handle have to consider this case and have to behave properly. I.e. define some behavior.

No. The only thing that must be valid to do with a value in .init state is destruct it. Standard library move et al. assume as much. In fact, what they do is exactly that - wipe out the moved from value, emplacing the initializer there. What also *may* be valid is reinitialization/assignment.

>> Sure, I could put in a runtime check, but then I get runtime errors and I rather have compile time errors.
>
> This is not an answer to my question. The question was: why you would like to have a different behavior w.r.t. some other behavior, which exists, independent of your actions.

Because .init does not generally exert any useful behavior.
November 27, 2018
On Tuesday, November 27, 2018 5:37:29 AM MST Stanislav Blinov via Digitalmars-d wrote:
> On Tuesday, 27 November 2018 at 12:14:17 UTC, Jonathan M Davis
>
> wrote:
> > Well, if the compiler were doing the move...
>
> [wall snipped]
>
> > And simply flagging common functions that do moves isn't going to be enough to catch all cases - especially when all it takes to hide the fact that a move occurred is to wrap the move call in another function that the compiler doesn't have visibility into thanks to separate compilation.
>
> "Easy" enough. The move and emplace families should be compiler intrinsics, not library functions.

Well, they're not currently. And even if they were, that doesn't solve the problem of detecting when functions are called that do moves that the compiler does not have the source code for due to separate compilation or because the move is actually being done in C code that gets called from D code. Without that, at best, you're catching the really simple cases, and even that likely requires code flow analysis. It also has the problem that it gives the impression that the compiler catches a certain class of problem for you in general when in fact it only catches it in a few cases. It's basically getting into the same territory as detecting when a pointer or reference is never initialized with anything other null and making that an error - something that Walter has already refused to do, because only the most basic cases would be caught (especially if you want to avoid false positives), and he doesn't want to add features that require code flow analysis.

Honestly, this seems like the sort of thing that's better suited to a linter that detects some cases where it thinks you might be accessing a variable in an invalid manner after it's been moved (allowing you to examine the code to see whether it's right or not), whereas the compiler has to get it right 100% of the time - especially if it's an error.

- Jonathan M Davis



November 27, 2018
On Tuesday, 27 November 2018 at 12:51:58 UTC, Stanislav Blinov wrote:
> On Tuesday, 27 November 2018 at 12:42:43 UTC, Alex wrote:
>> On Tuesday, 27 November 2018 at 12:03:20 UTC, Sebastiaan Koppe wrote:
>
>>>> auto val = Handle.init;
>>>>
>>>> 1. How do you treat this?
>>> I have no idea. Logically I would say that - in my case - the val is invalid.
>>>
>>
>> This is a source of bugs. The T.init value is the one and only value, which exists, independently from the definition (of Handle). Therefore, your own functions on Handle have to consider this case and have to behave properly. I.e. define some behavior.
>
> No. The only thing that must be valid to do with a value in .init state is destruct it.

I have something completely different in mind...
std.traits is full of examples, where T.init is used beyond of destruction.
And the behavior of a function for a .init value is also defined at all times. Independently from how the function is written. It can be easily tested by plugging the .init value as the input parameter.

> Standard library move et al. assume as much. In fact, what they do is exactly that - wipe out the moved from value, emplacing the initializer there. What also *may* be valid is reinitialization/assignment.

Sure. And I don't question this. I just would like to know, how the custom functions of OP handle the case of .init.
I mean... If they don't, then they assume the value is not in the .init state and perform some actions with it, which can lead to some assert. Which would be perfectly ok.

>
>>> Sure, I could put in a runtime check, but then I get runtime errors and I rather have compile time errors.
>>
>> This is not an answer to my question. The question was: why you would like to have a different behavior w.r.t. some other behavior, which exists, independent of your actions.
>
> Because .init does not generally exert any useful behavior.

Hmm... sure. But some behavior exists. And my question is: why (not even how) the behavior should differ between the .init case and the case after a move.
November 27, 2018
On Tuesday, 27 November 2018 at 13:17:24 UTC, Alex wrote:

> I have something completely different in mind...
> std.traits is full of examples, where T.init is used beyond of destruction.
> And the behavior of a function for a .init value is also defined at all times. Independently from how the function is written. It can be easily tested by plugging the .init value as the input parameter.

For compile-time checks, sure, as it is a convenient way to get at the underlying type. But that's all that is.

>> Standard library move et al. assume as much. In fact, what they do is exactly that - wipe out the moved from value, emplacing the initializer there. What also *may* be valid is reinitialization/assignment.
>
> Sure. And I don't question this. I just would like to know, how the custom functions of OP handle the case of .init.
> I mean... If they don't, then they assume the value is not in the .init state and perform some actions with it, which can lead to some assert. Which would be perfectly ok.

Right now we have to resort to runtime checks. Which is hilarious considering we *can* disable default construction, thereby asserting that .init is indeed an invalid state.

>>> This is not an answer to my question. The question was: why you would like to have a different behavior w.r.t. some other behavior, which exists, independent of your actions.
>>
>> Because .init does not generally exert any useful behavior.
>
> Hmm... sure. But some behavior exists. And my question is: why (not even how) the behavior should differ between the .init case and the case after a move.

What Sebastiaan proposes is for there to be *no* behavior at all, as using an invalid value would become a compile-time error. Which would mean one wouldn't need extraneous run-time checks.
November 27, 2018
On Tuesday, 27 November 2018 at 12:42:43 UTC, Alex wrote:
> On Tuesday, 27 November 2018 at 12:03:20 UTC, Sebastiaan Koppe
>> I have no idea. Logically I would say that - in my case - the val is invalid.
>>
> This is a source of bugs. The T.init value is the one and only value, which exists, independently from the definition (of Handle). Therefore, your own functions on Handle have to consider this case and have to behave properly. I.e. define some behavior.
>
> I mean, what would happen, if you take an existent function and pass Handle.init to it? Would it assert? Would it yield 42? Some reproducible behavior is defined well enough.

You are right, Handle.init is in the same state as it were after a move, so you would expect similar behaviour.
November 27, 2018
On Tuesday, 27 November 2018 at 14:37:40 UTC, Stanislav Blinov wrote:
> What Sebastiaan proposes is for there to be *no* behavior at all, as using an invalid value would become a compile-time error. Which would mean one wouldn't need extraneous run-time checks.

Yes. The only valid thing to do would be to move an existing Handle into it (explicitly or as a result of a function).
November 27, 2018
On Tuesday, 27 November 2018 at 14:37:40 UTC, Stanislav Blinov wrote:
> On Tuesday, 27 November 2018 at 13:17:24 UTC, Alex wrote:
>
> For compile-time checks, sure, as it is a convenient way to get at the underlying type. But that's all that is.
>
> Right now we have to resort to runtime checks. Which is hilarious considering we *can* disable default construction, thereby asserting that .init is indeed an invalid state.
>

Ok... so. Without default construction,

int i;
and
int i = void;

become the same. But you won't be able to prevent it from being an int, right?
How would you detect an invalid state in this case?

>
> What Sebastiaan proposes is for there to be *no* behavior at all, as using an invalid value would become a compile-time error. Which would mean one wouldn't need extraneous run-time checks.

I assume, that detecting invalid states becomes harder as types become simpler. And, it depends strongly on the application, whether a value is considered invalid... Doesn't Typedef already allow this functionality?
November 27, 2018
On Tuesday, 27 November 2018 at 15:14:13 UTC, Alex wrote:
> On Tuesday, 27 November 2018 at 14:37:40 UTC, Stanislav Blinov wrote:
>> On Tuesday, 27 November 2018 at 13:17:24 UTC, Alex wrote:
>>
>> For compile-time checks, sure, as it is a convenient way to get at the underlying type. But that's all that is.
>>
>> Right now we have to resort to runtime checks. Which is hilarious considering we *can* disable default construction, thereby asserting that .init is indeed an invalid state.
>>
>
> Ok... so. Without default construction,
>
> int i;
> and
> int i = void;
>
> become the same. But you won't be able to prevent it from being an int, right?
> How would you detect an invalid state in this case?

You wouldn't. int doesn't have an invalid state. That's not what's in question here. This is:

```
struct S {
    private X* ptr;
    @disable this();
    @disable this(this);
    this(X* p) { ptr = p; }
    ref get() return @safe { return *p; }
    alias get this;
}
```

...or pointers in general.

>> What Sebastiaan proposes is for there to be *no* behavior at all, as using an invalid value would become a compile-time error. Which would mean one wouldn't need extraneous run-time checks.
>
> I assume, that detecting invalid states becomes harder as types become simpler.

There's no difference. If the compiler would know what an invalid state is and what operations result in it, it doesn't matter what the type is.

> And, it depends strongly on the application, whether a value is considered invalid... Doesn't Typedef already allow this functionality?

No, it does not. We are talking about complie-time checking, not runtime checking.