October 03, 2020
On 10/3/20 12:56 PM, Mathias LANG wrote:
> On Saturday, 3 October 2020 at 13:05:43 UTC, Andrei Alexandrescu wrote:
>>
>> [...]
>>
>> * This has been discussed in C++ circles a number of times, and aliasing has always been a concern. If /C++/ deemed that too dangerous... <insert broadside>. A much more explicit solution has been implemented in https://www.boost.org/doc/libs/1_66_0/libs/utility/call_traits.htm.
> 
> I don't deny that aliasing can create issues that could be very hard to debug.

Cool. I hope we now agree there's evidence that such situations are not just hypothetical.

> But the problem of aliasing is not limited to `in`: code that uses `const ref` (or a  `const T` where `T` has indirection) can already misbehave if it doesn't take into account the possibility of parameter aliasing.
> 
> To put it differently: Why is `auto ref` acceptable but `in` is not ?

A good point. I can tell for myself. First, binding to reference vs. value is always reproducible the same regardless of platform particulars. Granted, maintenance is liable to introduce puzzlers but more of a first-order nature (change a call, get different resuts) as opposed to a long-distance issue (add a field to the object, suddenly unrelated code breaks - not to mention changes in compiler heuristics). Second, the keyword "ref" is in there, which is a clear giveaway the implementation code needs to expect that.
October 03, 2020
On 10/3/20 2:36 PM, Paul Backus wrote:
> On Saturday, 3 October 2020 at 16:56:06 UTC, Mathias LANG wrote:
>>
>> To put it differently: Why is `auto ref` acceptable but `in` is not ?
> 
> The issue with `in`, compared to `auto ref`, is that, because its behavior is implementation-defined, it invites programmers to write code that "works on their machine," but is not portable to other environments (including future versions of the same compiler). It's the same issue that C has with features like variable-sized integer types and implementation-defined signedness of `char`.
> 
> Yes, *technically* it's your fault if you write C code that relies on an `int` being 32 bits, or a `char` being unsigned, just like it would *technically* be your fault if you wrote D code that relied in an `in` parameter being passed by reference. But making these things implementation-defined in the first place is setting the programmer up for failure.

That's a very good comparison, thank you.
October 03, 2020
On Saturday, 3 October 2020 at 17:05:12 UTC, Steven Schveighoffer wrote:
> On 10/3/20 12:49 PM, Ola Fosheim Grøstad wrote:
>> On Saturday, 3 October 2020 at 15:58:53 UTC, Steven Schveighoffer wrote:
>>> Given that it's a parameter, and the parameter is const, it can only change through another reference. And this means, the function has to deal with the possibility that it can change, but ALSO cannot depend on or enforce being able to change it on purpose. On that, I think I agree with the concept of being able to switch to a value.
>> 
>> But you can expect it to not change in parallell as it is not shared!? It can change if you call another function or in the context of a coroutine (assuming that coroutines cannot move to other threads).
>
> You can expect it to change but due to the way it enters your function, you can't rely on that expectation, even today.
>
> For example:
>
> void foo(const ref int x, ref int y)
> {
>    auto z = x;
>    bar(); // might change x, but doesn't necessarily
>    y = 5; // might change x, but doesn't necessarily
> }
>
> So given that it *might* change x, but isn't *guaranteed* to change x, you can reason that the function needs to deal with both of these possibilities. There isn't a way to say "parameter which is an alias of this other parameter".
>

> In that sense, altering the function to actually accept x by value doesn't change what the function needs to deal with.

I am happy that we seem to agree on the principles, but I am a bit perplexed by this statement as we seem to draw different conclusions from the same principles...
:-D

I think maybe we have different use-cases in mind. So let me give you one. Assume that many SimulationObject instances form a graph accessible through a Simulation instance:

void run_and_print(const ref SimulationObject objview, ref Simulation world){
   auto old_state = objview.get_state();
   world.run_simulation();
   print_difference(old_state, objview.get_state());
}

If "objview" is turned into a value, nothing changes. objview is a view of one object in the "world" graph. A deliberate aliasing reference.

I don't want this behaviour from something named "ref", const or not const.

> And in a sense, you can rely on that. At a function level, you can't tell whether mutating other data is going to affect `in ref` or `const ref` data. You have to assume in some cases it can, and in some cases it cannot.

Do you mean the compiler or the programmer? As a programmer I most certainly can know this, which is why "__restricted__" semantics would be acceptable for me (although not newbie friendly).

> That being said, if `in` didn't have the definition it already has, this would not be as controversial.

I think it would be interesting if "in", "in out" and "out" had 100% compatible semantics with SPARK. D could get verification for free as the SPARK verifier is a separate tool. All you have to do is find a mapping from D to SPARK and generate the verification-condition output. (or just transpile to SPARK).

I think that could be a very powerful upgrade that could make D more favourable for embedded programming.

October 03, 2020
On Saturday, 3 October 2020 at 19:36:43 UTC, Ola Fosheim Grøstad wrote:
> On Saturday, 3 October 2020 at 17:05:12 UTC, Steven Schveighoffer wrote:
>>[...]
>
>> [...]
>
> I am happy that we seem to agree on the principles, but I am a bit perplexed by this statement as we seem to draw different conclusions from the same principles...
> :-D
>
> [...]

Wow. This would be very cool if it could be done (verification through SPARK)
October 03, 2020
On Saturday, 3 October 2020 at 19:47:43 UTC, Imperatorn wrote:
>
> Wow. This would be very cool if it could be done (verification through SPARK)

It would only be a small subset of D mapping to a subset of SPARK, but since the implementation effort would be reasonable if the basic semantics (parameter passing in particular) were mappable it could be interesting yes.

(But over time, maybe more of the language could be covered.)
October 03, 2020
On Saturday, 3 October 2020 at 18:36:57 UTC, Andrei Alexandrescu wrote:
> [...]
>
> A good point. I can tell for myself. First, binding to reference vs. value is always reproducible the same regardless of platform particulars. Granted, maintenance is liable to introduce puzzlers but more of a first-order nature (change a call, get different resuts) as opposed to a long-distance issue (add a field to the object, suddenly unrelated code breaks - not to mention changes in compiler heuristics). Second, the keyword "ref" is in there, which is a clear giveaway the implementation code needs to expect that.

This is missing the point: In an `auto ref` function, *the implementer of the function* not only cannot rely on the `ref`-ness of the parameter(s), but must plan for both, since it's almost a certainty that the function will be instantiated with both ref and non-ref. With `in`, only one state is possible at a time. For anything that isn't a POD, that `ref` state will be stable. So, while both `auto ref` and `in` need to plan for both `ref` state if they appear on a template parameter without constraint, `in` will always behave the same for non-POD type, while `auto ref` will not.

From the caller's point of view, it's also simpler with `in`. The same function will always be called, regardless of the lvalue-ness of the arguments provided. Hence, slight change at the call site, or in the surrounding context, that could affect lvalue-ness, will not lead to a different function being called, as it would for `auto ref`.

A platform dependent change of lvalue-ness is trivial to craft using only `auto ref`:
```
void foo () (auto ref size_t value)
{
    pragma(msg, __traits(isRef, value));
}
auto ref size_t platform (uint* param) { return *param; }
extern(C) void main ()
{
    uint value;
    foo(platform(&value));
}
```

Tested on Linux64:
```
% dmd -betterC -run previn.d
false
% dmd -betterC -m32 -run previn.d
true
```
October 03, 2020
On 10/3/20 5:36 PM, Mathias LANG wrote:
> On Saturday, 3 October 2020 at 18:36:57 UTC, Andrei Alexandrescu wrote:
>> [...]
>>
>> A good point. I can tell for myself. First, binding to reference vs. value is always reproducible the same regardless of platform particulars. Granted, maintenance is liable to introduce puzzlers but more of a first-order nature (change a call, get different resuts) as opposed to a long-distance issue (add a field to the object, suddenly unrelated code breaks - not to mention changes in compiler heuristics). Second, the keyword "ref" is in there, which is a clear giveaway the implementation code needs to expect that.
> 
> This is missing the point: In an `auto ref` function, *the implementer of the function* not only cannot rely on the `ref`-ness of the parameter(s), but must plan for both, since it's almost a certainty that the function will be instantiated with both ref and non-ref. With `in`, only one state is possible at a time. For anything that isn't a POD, that `ref` state will be stable. So, while both `auto ref` and `in` need to plan for both `ref` state if they appear on a template parameter without constraint, `in` will always behave the same for non-POD type, while `auto ref` will not.
> 
>  From the caller's point of view, it's also simpler with `in`. The same function will always be called

Not across long distance changes and platform particulars. This is a very important detail.

October 04, 2020
On Saturday, 3 October 2020 at 21:36:00 UTC, Mathias LANG wrote:
> A platform dependent change of lvalue-ness is trivial to craft using only `auto ref`:
> ```
> void foo () (auto ref size_t value)
> {
>     pragma(msg, __traits(isRef, value));
> }
> auto ref size_t platform (uint* param) { return *param; }
> extern(C) void main ()
> {
>     uint value;
>     foo(platform(&value));
> }
> ```
>
> Tested on Linux64:
> ```
> % dmd -betterC -run previn.d
> false
> % dmd -betterC -m32 -run previn.d
> true
> ```

This is a great illustration of the gotchas inherent in using variable-sized integer types like `size_t`.
October 04, 2020
On 10/2/2020 2:57 PM, Daniel N wrote:
> Also see the formally accepted DIP1021!
> https://github.com/dlang/DIPs/blob/148001a963f5d6e090bb6beef5caf9854372d0bc/DIPs/accepted/DIP1021.md 

That picks up the obvious cases, but the more subtle ones require @live's Data Flow Analysis to find.
October 04, 2020
On Saturday, 3 October 2020 at 21:36:00 UTC, Mathias LANG wrote:
> A platform dependent change of lvalue-ness is trivial to craft using only `auto ref`:
> ```
> void foo () (auto ref size_t value)
> {
>     pragma(msg, __traits(isRef, value));
> }
> auto ref size_t platform (uint* param) { return *param; }
> extern(C) void main ()
> {
>     uint value;
>     foo(platform(&value));
> }
> ```
>
> Tested on Linux64:
> ```
> % dmd -betterC -run previn.d
> false
> % dmd -betterC -m32 -run previn.d
> true
> ```

Platform-dependent, yes -- but the behavioural difference here can be 100% anticipated from the language rules (and hence, from the code), no?

Isn't the issue with `-preview="in"` as currently implemented that _there is no way_ for the reader of the code to anticipate when ref will be used and when not?