An important potential change to the language: transitory ref
Andrei Alexandrescu
SeeWebsiteForEmail at erdani.org
Fri Mar 19 23:53:50 PDT 2010
On 03/20/2010 12:56 AM, Steven Schveighoffer wrote:
> On Sat, 20 Mar 2010 00:07:55 -0400, Andrei Alexandrescu
> <SeeWebsiteForEmail at erdani.org> wrote:
>> I remember you brought up a similar point in a related discussion a
>> couple of years ago. It's a good point, and my current understanding
>> of the matter is that functions that take and return ref could and
>> should be handled conservatively.
>
> I don't like the sound of that... What I fear is that the compiler will
> force people to start using pointers because refs don't cut it. I'm
> guessing you mean you cannot return ref returns from other functions?
> That breaks abstraction principles, I should be able to delegate a task
> to a sub-function.
Perhaps it means you can't return ref returns from other functions if
you pass them references to local state.
(I've read a paper at some point about a program analysis that stored
for each function the "return pattern" - a mini-graph describing the
relationship between parameters and result. If it rings a bell to
anyone... please chime in.)
>>> For instance, try to find a rule that prevents the above from compiling,
>>> but allows the following to compile.
>>>
>>> struct S
>>> {
>>> private int x;
>>> ref int getX() { return x;}
>>> }
>>>
>>> struct T
>>> {
>>> S s;
>>> ref int getSX() { return s.x; }
>>> }
>>
>> In the approach discussed with Walter, S is illegal. A struct can't
>> define a method to return a reference to a direct member. This is
>> exactly the advice given in Scott's book for C++. (A class can because
>> classes sit on the heap.)
>
> A struct may sit on the heap too.
Yes. For those cases you can always use pointers, which are not subject
to the restrictions I envision for ref.
It's a very small inconvenience. For example, if you have a linked list
struct, you may feel constrained that you can't do:
struct List {
List * next;
List * prepend(List * lst) {
lst.next = &this;
return lst;
}
}
In my approach, &this is illegal. And actually for a good reason. This
code bombs:
List iForgotThePointer() {
List lst;
lst.prepend(new List);
return lst;
}
My response to the above issue is two-pronged:
(a) For List a class would be an alternative
(b) To work with pointers to structs use static member functions and
pointers instead of methods and references
> I don't know how else to describe it,
> but it feels like you are applying the solution in the wrong place. I
> understand what you are trying to solve, but your solution may be too
> blunt an instrument.
The goal is worth pursuing, so let's keep on thinking of how to make it
work. If D manages to define demonstrably safe encapsulated containers,
that would be an absolutely huge win.
> Here's another case:
>
> struct S
> {
> int*x;
> ref int getX() {return *x;}
> }
>
> Is x on the heap or not? How do you know? Arrays are just a wrapped
> pointer, so they too could be stack allocated.
struct S
{
int*x;
static ref int getX(S * p) {return *p.x;}
}
In an ideal world, if you have your hands on a pointer to a struct, you
should be reasonably certain that that lives on the heap. It would be
just great if D could guarantee that.
> Consider this:
>
> void foo(ref int x)
> {
> x++;
> }
>
> struct S
> {
> int x;
> int y;
> bool xisy;
> ref int getX() {if(xisy) return y; return x;}
> }
>
> foo(S.x);
> foo(S.getX());
Hm, I assume the two lines refer to an object of type S. The example
above would again have to be rewritten in terms of static functions with
pointers.
> Another case:
>
> struct S
> {
> int x;
> ref S opUnary(string op)() if (op == "++") {++x; return this;}
> }
>
> I feel this should all be possible.
I think opUnary should return void and the compiler should worry about
that result being used.
> ------
> counter proposal:
>
> What about having a new kind of ref that can only be passed up the
> stack, or down only one level if you are the one who initiated it.
>
> Call it scope ref:
>
> ref int baz(ref y)
> {
> return y;
> }
> scope ref int foo(scope ref int x, ref int y)
> {
> //return x; // illegal, we did not make x scope ref
> //return baz(x); // illegal, cannot convert scope ref into ref
> return y; // legal, you can convert a ref parameter into scope ref.
> }
>
> scope ref int bar()
> {
> int y;
> //return foo(y, y); //illegal, you cannot pass scope refs down the stack
> more than one level
> }
>
> At least this leaves ref alone to be used without restrictions that the
> compiler can't prove are necessary. If we find scope ref is the only
> kind of ref we ever use, then maybe we can get rid of scope ref and just
> make ref be the restricted form. Or you could keep scope ref and reserve
> ref for only provable heap-variables.
>
> Man, it would be nice to have escape analysis...
It sure would, but it quickly gets into the interprocedural tarpit.
Your idea is good, except I don't see why not make ref scoped ref. After
all ref is currently not an enabler - it could be missing from the
language; pointers are fine. So why not make ref do something actually
interesting?
Andrei
More information about the Digitalmars-d
mailing list