DIP69 - Implement scope for escape proof references

via Digitalmars-d digitalmars-d at puremagic.com
Sat Dec 6 05:30:03 PST 2014


On Friday, 5 December 2014 at 23:58:41 UTC, Walter Bright wrote:
> On 12/5/2014 8:48 AM, "Marc Schütz" <schuetzm at gmx.net>" wrote:
>> 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
>
> right, although:
>      evil = t;  // Error: not allowed
>
>>     }
>>
>> 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.
>
> As you point out, 'ref' is designed for this.

I wouldn't call it "designed", but "repurposed"...

>>     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.
>
> Right, though the compiler can optimize to produce the 
> equivalent.

??? This is a problem on the semantic level, unrelated to 
optimization:

     // contrived example to illustrated the point
     Container c;
     scope ref x = c[42];   // not
     scope ref y = c[44];   // ...
     scope ref z = c[13];   // allowed
     foo(x, y, z, x+y, y+z, z+x, x+y+z);

     // workaround, but error-prone and has different semantics
     // (opIndex may have side effects, called multiple times)
     foo(c[42], c[44], c[13], c[42]+c[44], c[44]+c[13], 
c[13]+c[42], c[42]+c[44]+c[13]);

     // another workaround, same semantics, but ugly and unreadable
     (scope ref int x, scope ref int y, scope ref int z) {
         foo(x, y, z, x+y, y+z, z+x, x+y+z);
     }(c[42], c[44], c[13]);

>> 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.
>
> I suspect that one can encapsulate such values in a struct 
> where access to them is strictly controlled.

They can, but again at the cost of an indirection (ref).

>> 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.
>
> Right. Different overloads can have different semantic 
> implementations, so what should inference do? I also suspect it 
> is bad style to overload on 'scope'.

It only makes sense with scope value types (see the RC example). 
For references, I don't know any useful applications.

>> 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();
>
> yes
>
>>     foo().bar2().baz();
>
> no - cannot return scope ref parameter
>
>>     foo().bar3().baz();
>
> yes
>
>>     foo().bar4().baz();
>
> no, cannot return scope ref parameter
>
>
>> I'm not sure I understand this fully yet, but it could be that 
>> none of them work...
>
> Well, you're half right :-)

Ok, so let's drop bar2() and bar4().

     scope ref int foo();
     scope ref int bar1(ref int a) {
         return a;
     }
     ref int bar3(ref int a) {
         return a;
     }
     ref int baz_noscope(/*scope*/ ref int a);

     foo().bar1().baz_noscope();
     foo().bar3().baz_noscope();

And now? In particular, will the return value of `bar3` be 
treated as if it were `scope ref`?


More information about the Digitalmars-d mailing list