On Friday, 5 December 2014 at 23:58:41 UTC, Walter Bright wrote:
On 12/5/2014 8:48 AM, "Marc Schütz" <[email protected]>" 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`?

Reply via email to