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

Reply via email to