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...

Reply via email to