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!

Reply via email to