Extend return semantics so that a 'return' parameter for void or constructor functions
applies to the first parameter, if it is a ref or out parameter. For a member function,
the first parameter is 'this'.
Currently, a 'return' parameter transfers its lifetime to the function return value:
```
int* frank(return scope int* p) { return p; }
int* p;
int i;
int* q;
q = frank(&i); // ok
p = frank(&i); // error
```
However, the following just issues errors:
```
@safe void betty(ref scope int* r, return scope int* p) { r = p; } // (1) error
int* p;
int i;
int* q;
betty(q, &i); // (2) ok
betty(p, &i); // (3) should be error
```
Marking `betty` as `@trusted` resolves (1), but does not detect the difference
between (2) and (3). Hence, the callers of `betty` would have to be marked
`@trusted` as well.
This situation comes up repeatedly with:
1. constructors
2. property setters
3. put(dest, source) functions
Annotating a parameter with `return` has been quite successful at tracking
scope dependencies from the parameter to the function return value. Extending
this in cases where there is no return value to the first parameter resolves
the issue identified above.
The scope check for:
```
void betty(ref scope int* r, return scope int* p);
betty(q, &i);
```
is as if `q = &i;` was encountered.
This is not new syntax, but new semantics for cases that were compile-time
errors before (i.e. annotating parameters with `return` when there was no
return value).
Trying to dissect Walter's back-of-the-napkin description:
Example 1
---------
@safe:
int* frank(return scope int* p) { return p; }
void main()
{
// lifetimes end in reverse order from which they are declared
int* p; // `p`'s lifetime is longer than `i`'s
int i; // `i`'s lifetime is longer than `q`'s
int* q; // `q`'s lifetime is the shortest
q = frank(&i); // ok because `i`'s lifetime is longer than `q`'s
p = frank(&i); // error because `i`'s lifetime is shorter than `p`'s
}
This works in the compiler today if both functions are declared @safe and if compiled with -dip1000: https://run.dlang.io/is/CZ3YuU
Comment #3 by slavo5150 — 2018-08-18T06:31:16Z
Example 2
---------
@safe:
void betty(ref scope int* r, return scope int* p)
{
r = p; // (1) Error: scope variable `p` assigned to `r` with longer lifetime
}
void main()
{
int* p;
int i;
int* q;
betty(q, &i); // (2) ok
betty(p, &i); // (3) should be error
}
Compile with -dip1000: https://run.dlang.io/is/t6wj71
So, I'm assuming (1) should not be an error because it depends on the lifetimes of the arguments supplied by the caller. Correct?
Comment #4 by slavo5150 — 2018-08-18T06:42:55Z
> This situation comes up repeatedly with:
> 1. constructors
> 2. property setters
> 3. put(dest, source) functions
In other words 2 and 3 are functions that return `void`. Constructors are like `static` functions that return an object instance, so I'm not sure how the problem at hand applies to constructors.
> Annotating a parameter with `return` has been quite successful at tracking
scope dependencies from the parameter to the function return value.
The problem with that is we don't have sufficient documentation describing the semantics of `return` parameters and their relationship with `scope`. So, I can't understand how `return` has been used in the past to solve such issues.
So `betty` returns `void`, but a `return` parameter transfers its lifetime to the function return value. So I guess that's the nature of this issue. How does `return` apply to a function that has no `return` type?
Comment #5 by bugzilla — 2018-08-18T11:06:31Z
(In reply to Mike Franklin from comment #4)
> How does `return` apply to a function that has no `return` type?
If the return type is 'void', and the first parameter is by 'ref', the 'return' applies to the first parameter. That's the whole thing in one sentence.
(For member functions, the first parameter is the implicit 'this' reference.)
@Mike It applies to constructors in the same way it applies to `void` functions whose first argument is `ref` or `out`. The hidden first parameter to the constructor is a `ref` parameter: `this`.
I think a better way to describe this issue is that first parameters that are `ref` or `out` (including `this` for constructors) should be considered and treated the same as return values for other functions.
Comment #8 by schveiguy — 2018-08-21T20:20:21Z
Just throwing this out there, why do we need it to be the first parameter? Why not ALL ref parameters?
It makes sense for a constructor, for which there is an obvious return expectation of the `this` parameter, but anyone can devise some calling scheme by which any arguments are transferred to any other ref arguments.
Just because Phobos follows the convention of putting the "return" parameter as the first parameter, does that mean the language should require it?
Comment #9 by slavo5150 — 2018-08-22T00:06:28Z
(In reply to Steven Schveighoffer from comment #8)
> Just because Phobos follows the convention of putting the "return" parameter
> as the first parameter, does that mean the language should require it?
I don't think that's necessarily true. Take a look at https://dlang.org/phobos/std_algorithm_mutation.html#copy
`TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target)`
Is copy an exception?
I recently asked about this convention at https://github.com/dlang/druntime/pull/2264#pullrequestreview-143076892
@safe void betty(bool b, ref scope int* r, return scope int* p)
{
if (b) r = p;
}
betty("", q, &i);
This is not going to work, I take it? Is that desirable?
Comment #12 by slavo5150 — 2018-12-01T06:01:07Z
I'm wondering if it might be possible to do something like this:
```
void betty(ref scope int* r, return(r) scope int* p)
{
r = p;
}
```
`return(r)` explicitly ties p's lifetime to r's.
So, the `return` attribute would take an argument specifying which "output" parameter to tie the input lifetime to, and the semantics no longer rely on convention or the order of arguments.
Comment #13 by slavo5150 — 2018-12-01T06:13:21Z
For constructors, one could use `return(this)` to the same effect. Perhaps for constructors, if there are no other "output" parameters in the signature, `return(this)` could be inferred by simply typing add `return` to the input parameters. If there is more than one "output" parameter, the user would have to explicitly disambiguate by adding an argument to the `return` attribute.
struct S
{
// does `return` apply to `this` or `r`?
this(ref scope int* r, return scope int* p);
// disambiguate by explicitly adding an argument to `return`
this(ref scope int* r, return(this) scope int* p);
// or
this(ref scope int* r, return(r) scope int* p);
}
Comment #14 by slavo5150 — 2018-12-01T07:22:26Z
Here's a little more about what I'm thinking:
struct S
{
// Error: `return` must be disambiguated between `this` and `r`
this(ref scope int* r, return scope int* p);
// OK, `return` has been disambiguated
this(ref scope int* r, return(this) scope int* p);
this(ref scope int* r, return(r) scope int* p);
// OK, only one possible *output*
int* f(return scope int* p);
// OK, only one possible *output*
void f(ref scope int* r, return scope int* p);
// Error: `return` must be disambiguated between `this` and `r`
void f(ref scope int* r1, ref scope int* r2, return scope int* p);
// OK, `return` has been disambiguated
void f(ref scope int* r1, ref scope int* r2, return(r1) scope int* p);
void f(ref scope int* r1, ref scope int* r2, return(r2) scope int* p);
// Error: `return` must be disambiguated between the return value
// and the `r` output parameter.
int* f(ref scope int* r, return scope int* p);
// OK, `return` has been disambiguated
int* f(ref scope int* r, return(r) scope int* p);
// OK, `return` has been disambiguated
// `return(return)` is pretty yucky, but I can't think of anything
// else right now.
int* f(ref scope int* r, return(return) scope int* p);
}
Comment #15 by slavo5150 — 2018-12-01T07:43:39Z
I think in my prior example, I may have overlooked the implicit `this` argument for member functions. Here I attempt to compensate.
//------------------------------------------------
// OK, only one possible *output*
int* f(return scope int* p);
// OK, only one possible *output*
void f(ref scope int* r, return scope int* p);
//------------------------------------------------
// Error: `return` must be disambiguated between `this` and `r`
void f(ref scope int* r1, ref scope int* r2, return scope int* p);
// OK, `return` has been disambiguated
void f(ref scope int* r1, ref scope int* r2, return(r1) scope int* p);
void f(ref scope int* r1, ref scope int* r2, return(r2) scope int* p);
//------------------------------------------------
// Error: `return` must be disambiguated between the return value
// and the `r` output parameter.
int* f(ref scope int* r, return scope int* p);
// OK, `return` has been disambiguated
int* f(ref scope int* r, return(r) scope int* p);
// OK, `return` has been disambiguated
// `return(return)` is pretty yucky, but I can't think of anything
// else right now.
int* f(ref scope int* r, return(return) scope int* p);
struct S
{
// Error: `return` must be disambiguated between `this` and `r`
this(ref scope int* r, return scope int* p);
// OK, `return` has been disambiguated
this(ref scope int* r, return(this) scope int* p);
this(ref scope int* r, return(r) scope int* p);
//------------------------------------------------
// Error: `return` must be disambiguated between `this` and `r`
void f(ref scope int* r, return scope int* p);
// OK, `return` has been disambiguated
void f(ref scope int* r, return(this) scope int* p);
// OK, `return` has been disambiguated
void f(ref scope int* r, return(r) scope int* p);
//------------------------------------------------
// Error: `return` must be disambiguated between `this`, `r1`, and `r2`
void f(ref scope int* r1, ref scope int* r2, return scope int* p);
// OK, `return` has been disambiguated
void f(ref scope int* r1, ref scope int* r2, return(r1) scope int* p);
void f(ref scope int* r1, ref scope int* r2, return(r2) scope int* p);
//------------------------------------------------
// Error: `return` must be disambiguated between `this` and the return value
int* f(return scope int* p);
// OK, `return` has been disambiguated
int* f(return(this) scope int* p);
// OK, `return` has been disambiguated - tied to return value
int* f(return(return) scope int* p);
}
Hopefully, you get the idea.