This looks like the best proposal so far. Many useful ideas, and at the
first glance it looks like a big step forward. I also really like how
the useful stack allocation behavior for closures and new'ed values is
kept alive.
What would be nice to add is a behavior specification for 'scope' member
variables (lifetime considered equal or slightly shorter than parent
object lifetime). For example the `RefCountedSlice.payload` and `count`
fields could be annotated with 'scope' to let the compiler actually
guarantee that they won't be accessible in a way that conflicts with
their lifetime (i.e. the 'scope' return of 'opIndex' would actually be
enforced).
That will just leave one hole in conjunction with the @trusted
destructor, which is (presumably) not easy to fix without much larger
changes to the type system, as well as to how container types are built.
It is still vulnerable to artificial shortening of the elements'
lifetime, e.g. by using opAssign() or destroy():
@safe {
RefCountedSlice!int s = ...;
scope int* el;
el = &s[0];
s = RefCountedSlice.init;
*el = 12; // oops
}
A similar issue affects the library implementation of isolated memory
that I did a while ago:
@safe {
class C { int* x; }
// c is guaranteed to be only reachable through this variable
Isolated!C c = makeIsolated!C();
// c.x is a @property that returns a specially wrapped reference to
// the actual C.x field - with this DIP this is similar to a 'scope'
// return, but acts transitively
Scoped!(int*) x = c.x;
// one of the benefits of Isolated!T is that it allows @safe
// conversion to immutable:
immutable(C) ci = c.freeze();
// c gets cleared by freeze() to disallow any further modifications
// but, oops, x is still there and can be used to modify the now
// immutable contents of ci.x
*x = 12;
}
Disallowing the assignment of scope return references to local scope
references (either by default, or using some form of additional
inference/annotation) would solve this particular issue, but not the
issue in general (the assignment/destruction could for example happen in
a nested function call).