[DIP idea] out variables
Q. Schroll
qs.il.paperinik at gmail.com
Tue Jan 26 01:01:54 UTC 2021
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?
More information about the Digitalmars-d
mailing list