[DIP idea] out variables

12345swordy alexanderheistermann at gmail.com
Tue Jan 26 02:44:20 UTC 2021


On Tuesday, 26 January 2021 at 01:01:54 UTC, Q. Schroll wrote:
> Main goal: Make the `out` parameter storage class live up to 
> promises.
> In current semantics, `out` is basically `ref` but with 
> documented intent. The initialization of the parameter is more 
> like a detail.
>
> General Idea
> ============
>
> The idea of an out variable is one that **must** be passed to a 
> function in an `out` parameter position. Basic example:
>
>     int f(out int value);
>     int g(int[] value...);
>     int h(out int a, out int b);
>
>     out int x;
>     // g(x); // illegal: reads x, but x is not yet initialized.
>     // h(x, x); // illegal:
>         // reads the second x before the initialization of 
> first x is complete.
>     f(x); // initializes x.
>
> An `out` variable cannot be read until initialized by a 
> function call in an `out` parameter position. Since D has exact 
> evaluation order, it is easily determined that one usage of `x` 
> initializes it and another in the same overall expression reads 
> it (and not the other way around):
>
>     out int x, y;
>     /*1*/ if (h(x, y) > 0 && x < y) { .. }
>     /*2*/ g(f(x), f(y), x, y);
>
> Evaluation order says in /*1*/ that h(x, y) is executed before 
> x and y are read for testing `x < y`.
> Evaluation order says in /*2*/ that f(x) and f(y) are executed 
> before x and y are read for passing them to g.
>
> Also, multiple execution paths can lead to different 
> initialization points:
>
>     out int x, y, z;
>     if (g(0)) { f(x); f(y); f(z); } else h(x, y);
>     // x, y are initialized.
>     g(x, y); // okay: x and y initialized on both branches
>     g(z); // invalid: z might not be initialized.
>
> It is always possible to initialize `out` variables using an 
> ordinary assignment:
>
>     out int x, y, z;
>     if (g(0)) { /*as above*/ } else { h(x, y); z = 0; }
>     g(z); // valid: z initialized on both branches
>
>
> Templates
> =========
>
> Similar to `ref`, there will be `auto out` which infers `out` 
> based on the arguments passed. `auto out` can be combined with 
> `ref` (meaning pass by reference always, but if the argument is 
> an out value, this is its initialization) and `auto ref` 
> (meaning pass by reference if possible, and if the argument is 
> an out value, this is its initialization; it cannot be passed 
> by value and be initialized).
>
> With __traits(isOut, param) one can test whether `auto out` 
> boiled down to `out` or not.
>
> After being (potentially|definitely|?) initialized, `out` 
> variables do not trigger `auto out` to become `out`.
>
>
> In-place `out` Variables
> ========================
>
> When calling a function with an `out` parameter, instead of 
> passing an argument, a fresh variable can be declared instead:
>
>     if (f(out int x) > 0 && x > 0) { g(x); } else { .. }
>     if (g(0) && f(out x) > 0) { g(x); } else { .. }
>
> The type of an in-place out variable can be left out, when it 
> can be inferred from the called function. [Clearly it can be 
> done in some cases and clearly it cannot be in all templates. 
> Exact rules TBD.]
> In the first else branch, `x` can be used, since regardless 
> whether the `f(out int x) > 0 && x > 0` is true or false, 
> evaluating it will initialize `x`.
> In the second else branch, `x` cannot be used because `x` might 
> not be initialized if g(0) is false.
> The visibility of in-place out variables is limited to the 
> statement they're declared in. For `if` statements this 
> encompasses both branches, but for expression statements, it 
> only encompasses that expression:
>
>     x = f(out a) + a; // valid
>     y = f(out b);
>     // y += b; // error, b not visible
>     out int c;
>     f(c);
>     z += c; // valid
>
> One obvious use-case is functions that return a bool value 
> indicating success and the result is an `out` parameter. 
> Usually, these functions' names begin with try:
>
>     if (tryParseInt(str, out x)) { use(x); }
>
> Another could be unpacking:
>
>     out T x;
>     out S y;
>     tuple.unpack(x, y);
>     // or
>     if (tuple.unpack(out a, out b) && condition(a, b)) { .. }
>
> What do you think? Worth it?

in, out, inout need some badly reworking to do. Their is a 
preview for in, but not for others sadly.

-Alex


More information about the Digitalmars-d mailing list