DIP69 - Implement scope for escape proof references

via Digitalmars-d digitalmars-d at puremagic.com
Fri Dec 5 08:48:43 PST 2014


There are limitations this proposal has in comparison to my 
original one. These limitations might of course be harmless and 
play no role in practice, but on the other hand, they may, so I 
think it's good to list them here.

Additionally I have to agree with Steven Schveighoffer: This DIP 
is very complicated to understand. It's not obvious how the 
various parts play together, and why/to which degree it "works", 
and which are the limitations. I don't think that's only because 
my brain is already locked on my proposal...

1) Escape detection is limited to `ref`.

     T* evil;
     ref T func(scope ref T t, ref T u) @safe {
       return t; // Error: escaping scope ref t
       return u; // ok
       evil = &u; // Error: escaping reference
     }

vs.

     T[] evil;
     T[] func(scope T[] t, T[] u) @safe {
       return t; // Error: cannot return scope
       return u; // ok
       evil = u; // !!! not good
     }

As can be seen, `ref T u` is protected from escaping (apart from 
returning it), while `T[] u` in the second example is not. 
There's no general way to express that `u` can only be returned 
from the function, but will not be retained otherwise by storing 
it in a global variable. Adding `pure` can express this in many 
cases, but is, of course, not always possible.

Another workaround is passing the parameters as `ref`, but this 
would introduce an additional indirection and has different 
semantics (e.g. when the lengths of the slices are modified).

2) `scope ref` return values cannot be stored.

     scope ref int foo();
     void bar(scope ref int a);

     foo().bar();        // allowed
     scope tmp = foo();  // not allowed
     tmp.bar();

Another example:

     struct Container(T) {
         scope ref T opIndex(size_t index);
     }

     void bar(scope ref int a);

     Container c;
     bar(c[42]);            // ok
     scope ref tmp = c[42]; // nope

Both cases should be fine theoretically; the "real" owner lives 
longer than `tmp`. Unfortunately the compiler doesn't know about 
this.

Both restrictions 1) and 2) are because there are no explicit 
lifetime/owner designations (the scope!identifier thingy in my 
proposal).

3) `scope` cannot be used for value types.

I can think of a few use cases for scoped value types (RC and 
file descriptors), but they might only be marginal.

4) No overloading on `scope`.

This is at least partially a consequence of `scope` inference. I 
think overloading can be made to work in the presence of 
inference, but I haven't thought it through.

5) `scope` is a storage class.

Manu complained about `ref` being a storage class. If I 
understand him right, one reason is that we have a large toolkit 
for dealing with type modifiers, but almost nothing for storage 
classes. I have to agree with him there. But I haven't understood 
his point fully, maybe he himself can post more about his 
problems with this?

6) There seem to be problems with chaining.

     scope ref int foo();
     scope ref int bar1(ref int a) {
         return a;
     }
     scope ref int bar2(scope ref int a) {
         return a;
     }
     ref int bar3(ref int a) {
         return a;
     }
     ref int bar4(scope ref int a) {
         return a;
     }
     void baz(scope ref int a);

Which of the following calls would work?

     foo().bar1().baz();
     foo().bar2().baz();
     foo().bar3().baz();
     foo().bar4().baz();

I'm not sure I understand this fully yet, but it could be that 
none of them work...


More information about the Digitalmars-d mailing list