DIP 1016--ref T accepts r-values--Formal Assessment

Andrei Alexandrescu SeeWebsiteForEmail at erdani.com
Mon Jan 28 19:58:24 UTC 2019


On 1/28/19 1:00 PM, Andrei Alexandrescu wrote:
> On 1/24/19 3:01 PM, kinke wrote:
>> On Thursday, 24 January 2019 at 09:49:14 UTC, Manu wrote:
>>> We discussed and concluded that one mechanism to mitigate this issue
>>> was already readily available, and it's just that 'out' gains a much
>>> greater sense of identity (which is actually a positive side-effect if
>>> you ask me!).
>>> You have a stronger motivation to use 'out' appropriately, because it
>>> can issue compile errors if you accidentally supply an rvalue.
>>
>> `out` with current semantics cannot be used as drop-in replacement for 
>> shared in-/output ref params, as `out` params are default-initialized 
>> on entry. Ignoring backwards compatibility for a second, I think 
>> getting rid of that would actually be beneficial (most args are 
>> probably already default-initialized by the callee in the line above 
>> the call...) - and I'd prefer an explicitly required `out` at the call 
>> site (C# style), to make the side effect clearly visible.
>>
>> I'd have otherwise proposed a `@noRVal` param UDA, but redefining 
>> `out` is too tempting indeed. ;)
> 
> It seems to me that a proposal adding the "@rvalue" attribute in 
> function signatures to each parameter that would accept either an rvalue 
> or an lvalue would be easy to argue.
> 
> - No exposing existing APIs to wrong uses
> - The function's writer makes the decision ("I'm fine with this function 
> taking an rvalue")
> - Appears in the function's documentation
> - Syntax is light and localized where it belongs
> - Scales well with number of parameters
> - Transparent to callers
> 
> Whether existing keyword combinations ("in", "out", "ref" etc) could be 
> used is a secondary point.
> 
> The advantage is there's a simple and clear path forward for API 
> definition and use.
> 
> 
> Andrei

One more thought.

The main danger is restricted to a specific conversion: lvalue of type T 
is converted to ref of type U. That way both the caller and the function 
writer believe the value gets updated, when in fact it doesn't. Consider:

real modf(real x, ref real i);

Stores integral part in i, returns the fractional part. At this point 
there are two liabilities:

1. User passes the wrong parameter type:

double integral;
double frac = modf(x, integral);
// oops, integral is always NaN

The function silently converts integral from double to real and passes 
the resulting temporary into the function. The temporary is filled and 
lost, leaving user's value unchanged.

2. The API gets changed:

// Fine, let's use double
real modf(real x, ref double i);

At this point all correct callers are silently broken - everybody who 
correctly used a real for the integral part now has their call broken 
(real implicitly converts to a double temporary, and the change does not 
propagate to the user's value).

(If the example looks familiar it may be because of 
https://dlang.org/library/std/math/modf.html.)

So it seems that the real problem is that the participants wrongly 
believe an lvalue is updated.

But let's say the caller genuinely doesn't care about the integral part. 
To do so is awkward:

real unused;
double frac = modf(x, unused);

That code isn't any better or less dangerous than:

double frac = modf(x, double());

Here the user created willingly created an unnamed temporary of type 
double. Given that there's no doubt the user is not interested in that 
value after the call, the compiler could (in a proposed semantics) allow 
the conversion of the unnamed temporary to ref.

TL;DR: it could be argued that the only dangerous conversions are lvalue 
-> temp rvalue -> ref, so only disable those. The conversion rvalue -> 
temp rvalue -> ref is not dangerous because the starting value on the 
caller side could not be inspected after the call anyway.


Andrei


More information about the Digitalmars-d-announce mailing list