Jump to page: 1 25  
Page
Thread overview
Should out/ref parameters require the caller to specify out/ref like in C#?
May 28, 2017
WebFreak001
May 28, 2017
Moritz Maxeiner
May 28, 2017
Meta
May 28, 2017
ketmar
May 29, 2017
Meta
May 29, 2017
ketmar
May 29, 2017
ketmar
May 28, 2017
Stefan Koch
May 28, 2017
Stanislav Blinov
May 28, 2017
Stefan Koch
May 28, 2017
ketmar
May 28, 2017
Nicholas Wilson
May 29, 2017
ketmar
May 28, 2017
Jonathan M Davis
May 29, 2017
ketmar
May 29, 2017
Adam D. Ruppe
May 29, 2017
Dukc
May 29, 2017
Stanislav Blinov
May 29, 2017
Dukc
May 29, 2017
Jonathan M Davis
May 29, 2017
Jonathan M Davis
May 29, 2017
Jonathan M Davis
May 29, 2017
Stanislav Blinov
May 29, 2017
Jonathan M Davis
May 29, 2017
Stanislav Blinov
May 29, 2017
Jonathan M Davis
May 29, 2017
Stanislav Blinov
May 30, 2017
Jonathan M Davis
May 30, 2017
Stanislav Blinov
May 30, 2017
Stanislav Blinov
May 30, 2017
Stanislav Blinov
May 30, 2017
Stanislav Blinov
May 30, 2017
Jonathan M Davis
May 29, 2017
WebFreak001
May 29, 2017
Dukc
May 28, 2017
Imagine you wrote a function

void foo(ref int a) {
  if (std.random.uniform(0, 10) == 0)
    a = 0;
  // Actual code doing something
}

in your API you didn't document that this will change `a` (or we will assume the user simply didn't read because you would never do something like this).

The user now calls the code in his program, probably doesn't know that foo takes a as ref and because it's his first time using the function he doesn't expect it to either.

void main(string[] args) {
  int input = args[1].to!int + 1;
  writeln("Processing for ", input);
  foo(input);
  writeln(100 / input); // idk, it will crash if input == 0 though
}

Now his code will occasionally crash but the user can't figure out why and can't always reproduce it. Imagine the code is somewhere deep inside event handlers from some GUI library or recursive calls too.

Should the language spec say that those functions should get called with `foo(ref input);` so that surprises like this where the user doesn't check the docs/implementation can't happen (like in C#)?

I think the user should be enforced to use foo(ref input) instead of foo(input) as it greatly increases understanding of the code on the caller side and another advantage is that programs analyzing the AST can better understand if the argument is unused (DScanner could use this for example).

On the other hand a lot of code has been written without this already and especially a lot of UFCS code would break with this like for example functions acting like member functions of the ref argument. A fix for this might be just implying the ref you would add on an argument if you use UFCS but I'm not sure if that is really a good idea.

This post is just an idea because I think it can result in really confused users and that the usage both by library developer and user of out/ref in general is kind of bad by design because it will just imply it and it won't be visible in the code. Especially when changing the API this will result in many runtime errors that need to be discovered if the user doesn't read the changelog.

Especially because this would break a lot of code I don't really expect anything to happen but maybe this could be taken into account when D3 will be designed or be added in some optional DIP that can be used using a compiler flag?
May 28, 2017
On Sunday, 28 May 2017 at 17:54:30 UTC, WebFreak001 wrote:
> Imagine you wrote a function
>
> void foo(ref int a) {
>   if (std.random.uniform(0, 10) == 0)
>     a = 0;
>   // Actual code doing something
> }
>
> in your API you didn't document that this will change `a` (or we will assume the user simply didn't read because you would never do something like this).

Taking the parameter as `ref int` *is* documenting that you will mutate it. Otherwise, you would have taken it as `ref const int` instead.
Additionally, someone who does not read documentation is likely to have his/her program so riddled with bugs, that I don't think considering him/her is worth the effort.

>
> The user now calls the code in his program, probably doesn't know that foo takes a as ref and because it's his first time using the function he doesn't expect it to either.
>
> void main(string[] args) {
>   int input = args[1].to!int + 1;
>   writeln("Processing for ", input);
>   foo(input);
>   writeln(100 / input); // idk, it will crash if input == 0 though
> }
>
> Now his code will occasionally crash but the user can't figure out why and can't always reproduce it. Imagine the code is somewhere deep inside event handlers from some GUI library or recursive calls too.

Then you'll have to debug, yes. An argument to always consider `ref T` as "I will mutate this" and `ref const T` as "I just want to read from this" and to (almost) never cast away const.

>
> Should the language spec say that those functions should get called with `foo(ref input);` so that surprises like this where the user doesn't check the docs/implementation can't happen (like in C#)?

While I personally might like this syntax, what about const / immutable, does this then require `foo(ref const input)` and `foo(ref immutable input)`? Or `foo(ref cast(const int) input)`?

> [...]

I can see both points of your argument, but here is one more argument against it: It introduces a language inconsistency by special casing references in function calls. We would have to allow `ref` everywhere we already allow `&` for pointers to get rid of the inconsistency (regardless of whether it makes sense to allow `ref` elsewhere).

>
> Especially because this would break a lot of code I don't really expect anything to happen but maybe this could be taken into account when D3 will be designed or be added in some optional DIP that can be used using a compiler flag?

AFAIK D3 is hypothetical at this point.
May 28, 2017
On Sunday, 28 May 2017 at 17:54:30 UTC, WebFreak001 wrote:
> Imagine you wrote a function
>
> void foo(ref int a) {
>   if (std.random.uniform(0, 10) == 0)
>     a = 0;
>   // Actual code doing something
> }
>
> in your API you didn't document that this will change `a` (or we will assume the user simply didn't read because you would never do something like this).
>
> The user now calls the code in his program, probably doesn't know that foo takes a as ref and because it's his first time using the function he doesn't expect it to either.
>
> void main(string[] args) {
>   int input = args[1].to!int + 1;
>   writeln("Processing for ", input);
>   foo(input);
>   writeln(100 / input); // idk, it will crash if input == 0 though
> }
>
> Now his code will occasionally crash but the user can't figure out why and can't always reproduce it. Imagine the code is somewhere deep inside event handlers from some GUI library or recursive calls too.
>
> Should the language spec say that those functions should get called with `foo(ref input);` so that surprises like this where the user doesn't check the docs/implementation can't happen (like in C#)?
>
> I think the user should be enforced to use foo(ref input) instead of foo(input) as it greatly increases understanding of the code on the caller side and another advantage is that programs analyzing the AST can better understand if the argument is unused (DScanner could use this for example).
>
> On the other hand a lot of code has been written without this already and especially a lot of UFCS code would break with this like for example functions acting like member functions of the ref argument. A fix for this might be just implying the ref you would add on an argument if you use UFCS but I'm not sure if that is really a good idea.
>
> This post is just an idea because I think it can result in really confused users and that the usage both by library developer and user of out/ref in general is kind of bad by design because it will just imply it and it won't be visible in the code. Especially when changing the API this will result in many runtime errors that need to be discovered if the user doesn't read the changelog.
>
> Especially because this would break a lot of code I don't really expect anything to happen but maybe this could be taken into account when D3 will be designed or be added in some optional DIP that can be used using a compiler flag?

If a parameter is marked as ref then you have to assume it will be modified by the function (unless it's const/inout/immutable). If it's marked as out then you know it will be. If you didn't know that the function takes its parameters by ref or out... You're should've RTFM.
May 28, 2017
Meta wrote:

> If a parameter is marked as ref then you have to assume it will be modified by the function (unless it's const/inout/immutable). If it's marked as out then you know it will be. If you didn't know that the function takes its parameters by ref or out... You're should've RTFM.

now imagine that you're reading some code:

	foo(a);

vs:

	foo(ref a);

which code style is easier to read without constant jumping into documentation?
May 28, 2017
On Sunday, 28 May 2017 at 17:54:30 UTC, WebFreak001 wrote:
> Imagine you wrote a function
>
> void foo(ref int a) {
>   if (std.random.uniform(0, 10) == 0)
>     a = 0;
>   // Actual code doing something
> }
>
> [...]

Syntax wise we could force you to say foo(&something).
Which fits perfectly in the existing pointer syntax.


May 28, 2017
On Sunday, 28 May 2017 at 22:03:48 UTC, Stefan Koch wrote:
> On Sunday, 28 May 2017 at 17:54:30 UTC, WebFreak001 wrote:
>> Imagine you wrote a function
>>
>> void foo(ref int a) {
>>   if (std.random.uniform(0, 10) == 0)
>>     a = 0;
>>   // Actual code doing something
>> }
>>
>> [...]
>
> Syntax wise we could force you to say foo(&something).
> Which fits perfectly in the existing pointer syntax.

No it does not, because then this becomes ambiguous:

foo(ref int a);
foo(int* b);

...and furthermore, what would we do with this:

void foo1(T)(auto ref T a) { foo2(a); }
void foo2(T)(auto ref T a) { /*...*/ }

?
May 28, 2017
On Sunday, 28 May 2017 at 22:18:01 UTC, Stanislav Blinov wrote:
> On Sunday, 28 May 2017 at 22:03:48 UTC, Stefan Koch wrote:
>> On Sunday, 28 May 2017 at 17:54:30 UTC, WebFreak001 wrote:
>>> Imagine you wrote a function
>>>
>>> void foo(ref int a) {
>>>   if (std.random.uniform(0, 10) == 0)
>>>     a = 0;
>>>   // Actual code doing something
>>> }
>>>
>>> [...]
>>
>> Syntax wise we could force you to say foo(&something).
>> Which fits perfectly in the existing pointer syntax.
>
> No it does not, because then this becomes ambiguous:
>
> foo(ref int a);
> foo(int* b);
>
> ...and furthermore, what would we do with this:
>
> void foo1(T)(auto ref T a) { foo2(a); }
> void foo2(T)(auto ref T a) { /*...*/ }
>
> ?

Personally I stay away from ref precisely because of it's silent caller syntax.
May 29, 2017
Stefan Koch wrote:

> Personally I stay away from ref precisely because of it's silent caller syntax.

same here. i prefer to use pointers instead, 'cause they are visible at the calling site.
May 28, 2017
On Sunday, 28 May 2017 at 17:54:30 UTC, WebFreak001 wrote:
> Imagine you wrote a function
>
> void foo(ref int a) {
>   if (std.random.uniform(0, 10) == 0)
>     a = 0;
>   // Actual code doing something
> }
>
> [...]

It seems nice in theory but how will it interact with generic code?
Perhaps it should be optional and purely documentative.
May 28, 2017
On Sunday, May 28, 2017 17:54:30 WebFreak001 via Digitalmars-d wrote:
> I think the user should be enforced to use foo(ref input) instead
> of foo(input) as it greatly increases understanding of the code
> on the caller side and another advantage is that programs
> analyzing the AST can better understand if the argument is unused
> (DScanner could use this for example).

This has been discussed before. Among other things, it does not play nicely with UFCS or generic code (both of which are huge in D). If you really want to have it show at the call site that you're not simply passing by value, then use a pointer.

- Jonathan M Davis

« First   ‹ Prev
1 2 3 4 5