My thoughts & tries with rvalue references

Zach the Mystic reachzach at gggggmail.com
Sat Apr 6 00:50:09 PDT 2013


On Friday, 5 April 2013 at 07:45:33 UTC, Dicebot wrote:
> On Friday, 5 April 2013 at 00:12:33 UTC, Zach the Mystic wrote:
>> struct Large { ... }
>> ref Large process1(@temp ref Large a) { return a; }
>> ref Large process2(@temp ref Large a) { return a; }
>>
>> Large* lar = &process2(process1(Large("Pass"," a ", "Large", 
>> "here")));
>
> This is exactly type of code I consider to be bad style and 
> want to see banned. Function that gets rvalue ref should never 
> return it as it knows nothing about its lifetime. Actually, I 
> can't even find scope definition for temporaries in dlang.org, 
> but it is hardly a good thing to rely on anyway. Best is to 
> assume that when function is gone, so is rvalue temporary.

It may be bad style. (I don't know if it's bad style.) But there 
is significant thought (such as in DIP25) going into the idea 
that the programmer doesn't even *need* to track the lifetime of 
a reference, that the compiler can do it automatically. I don't 
think it's documented, but there is already an error issued for 
local variables which are returned by reference, which is the 
same behavior as 'scope' described.

ref int func() {
   int y;
   return y; // Error: may not be escaped
}

There is no current checking, however, for returning the result 
of a function which returns ref.

ref int func(ref int a) { return a; }
ref int func2() {
   int x;
   return func(x); // Passes when it should error, x escaped
}

DIP25, to be on the safe side, proposes that since func() takes a 
ref, it must be assumed to return the ref it takes. So func(x) 
will be treated as a local since x is a local. The only flaw with 
DIP25 is that it's actually *too* safe, and it will shut down 
some cases which are perfectly fine:

ref int copy(ref int a) {
   int *b = new int;
   *b = a;
   return *b; // return by ref
}

I have tried to address this issue elsewhere (and I actually need 
to make formal proposals which I haven't done yet), but the main 
point is that even accepting a temporary rvalue is the kind of 
thing which can be tracked at the call site, and not the function 
itself.

ref int func(ref int a) { return a; }
ref int func2() {
   int x;
   return func(x); // Error, according to DIP25
   return func(25); // Error also, temp 25 is local, just like x

   static int* y;
   return func(y); // Okay because y is not local
}

So there are two different issues going on.

Now what's the big deal, you may ask. Any function designed to 
accept an rvalue temporary can't possibly want to return that 
parameter by reference. And you may be right. In fact, the only 
reason I know to allow it was mentioned above, because it would 
allow efficient chaining of operations on a single entity passed 
by reference instead of by value, with each function modifying it 
before passing it on. But it's the *caller* who needs to know how 
local the reference is, not the receiving function. And that 
leads me into the other issue, which is that I had thought 
'scope' would be a great way to tell a calling function "no, this 
ref will not be returned to you". (This is the issue I need to 
make a formal proposal on.) And that leads to a direct 
contradiction with the proposed use of 'scope ref', as I have 
written in my answer to Kenji Hara's defense of 'scope ref'.

ref int func(scope int a) {
   return *new int; // Okay
   return a; //Error: can't return a parameter marked scope
}

Now the checking used by DIP25 could call func() safely:

ref int testFunc() {
   int x;
   return func(x); // Okay, I know x will not be returned
}

So 'scope' and 'scope ref' would mean two different things, and 
there might be some cases where you only want one of the features 
and not both.

To address this issue, I thought of a slightly desperate way to 
actually resolve this problem without any new storage class, such 
as 'ref&' or '@temp ref'. In order to indicate 'scope' in 
addition to 'scope ref', you'd simply write 'scope scope ref'. 
Two scopes! The defense of this position is that the actual use 
of 'scope' by itself would rarely be used, and so the strange 
appearance of two scopes would almost never happen.

> Your code begs for using plain refs and storing Large in a 
> stack variable before calling function chain. May look a bit 
> less convenient but much more reliable and understandable.

Yes, this issue would be simplified by simply saying that any 
function which accepts rvalue temporaries must treat those 
parameters as locals and not allow returning them. It imposes a 
minor inconvenience on the programmer who must declare an lvalue 
to use any function which *does* return a reference to the 
parameter. I actually think this is a sound design choice, but at 
least it will be a choice and not a "lucky" accident.

One last thing about 'scope ref', which would be usable for the 
new feature, assuming the design choice just mentioned was 
accepted. It's not as obvious that it implicitly allows rvalue 
temporaries as something blunt like '@temp ref' would be, or as 
inconspicuous as 'ref&' would be. Also, it sort of suggests that 
ordinary 'scope' is not in fact passed by ref, which I is 
somewhat misleading. So it does save on syntax creep, but it also 
has those three disadvantages.

I think that's all I have to say about this topic!


More information about the Digitalmars-d mailing list