On Thursday, 6 February 2014 at 15:53:01 UTC, Adam D. Ruppe wrote:
Sorry, my lines got mangled, let me try pasting it again.
Making scope the default
=======================
There's five points to discuss:
1) All variables are assumed to be marked with scope implicitly
2) The exception is structs with a special annotation which
marks that they encapsulate a resource. An encapsulated
resource explicitly marked scope at the usage site is STILL
scope, but it will not implicitly inherit the scopiness of the
member reference/
@encapsulated_resource
struct RefCounted(T) {
T t; // the scopiness of this would not propagated to
// refcounted itself
}
This lets us write structs to manage raw pointers (etc.) as an
escape from the rules. Note you may also write
@encaspulated_resource struct Borrowed(T){} as an escape from
the rules. Using this would of course be at your own risk,
analogous to @trusted code.
3) Built-in allocations return GC!T instead of T. GC!T's
definition is:
@encapsulated_resource
struct GC(T) {
private T _managed_payload;
/* @force_inline */
/* implicit scope return value */
@safe nothrow inout(T) borrow() { return _managed_payload; }
alias borrow this;
}
NOTE: if inout(T) there doesn't work for const correctness, we
need to fix const on wrapped types; an orthogonal issue.
If you don't care about ownership, the alias this gives you a
naked borrowed reference whenever needed. If you do care about
ownership:
auto foo = new Foo();
static assert(is(typeof(foo) == GC!Foo));
letting you store it with confidence without additional steps
or assumptions.
When passing to a template, if you want to explicitly borrow
it, you might write borrow. Otherwise, IFTI will see the whole
GC!T type. This is important if we want to write owned
identity templates.
If an argument is scope, ownership is irrelevant. We might
strip it off but I don't think that's necessary... might help
avoid template bloat though.
4) All other types remain the same. Yes, typeof(this) == T,
NEVER GC!T. Again, remember the rule of thumb: would this work
with as static stack buffer?
class Foo { Foo getMe() { return this; } }
ubyte[__traits(classInstanceSize, Foo)] buffer;
Foo f = emplace!Foo(buffer); // ok so far, f is scope
GC!Foo gc = f.getMe(); // obviously wrong, f is not GC
The object does not control its own allocation, so it does
not own its own memory. Thus, `this` is *always* borrowed.
Does this work if building a tree:
class Tree { Tree[] children; Tree addChild(Tree t) {
children ~= t; } }
addChild there would *not* compile, since it escapes the t
into the object's scope. Tree would need to know ownership:
make children and addChild take GC!Tree instead, for example,
then it will work.
What if addChild wants to set t.parent = this; ? That
wouldn't be possible (without using a trust-me borrowed!T
wrapper)... and while this would break some of my code... I say
unto you, such code was already broken, because the parent
might be emplaced on a stack buffer!
GC!Tree child = new Tree();
{
ubyte[...] stack;
Owned!Tree parent = emplace!Tree(stack[]);
parent.addChild(child);
}
child.parent; // bug city
Instead, addChild should request its own ownership.
Tree addChild(GC!Tree child, GC!Tree _this) {
children ~= child;
child.parent = _this;
}
Then, the buggy above scenario does not compile, while
making it possible to do the correct thing, storing a
(verified) GC reference in the object graph.
I understand that would be a bit of a pain, but you agree it
is more correct, yes? So that might be worthwhile breakage
(especailly since we're talking about potentially large
breakage already.)
5) Interaction with @safe is something we can debate. @safe
works best with the GC, but if we play our scope cards right,
memory corruption via stack stuff can be statically eliminated
too, thus making some varaints of emplace @safe too. So I don't
think even @safe functions can assume this == GC, and even if
they could, we shouldn't since it limits us from legitimate
optimizations.
So I think the @safe rules should stay exactly as they are
now. Wrapper structs that do things like malloc/realloc might
be @system because it would still be possible for a borrowed
pointer to be invalidated when they realloc (note this is not
the case with GC, which is @safe even through growth
reallocations). So @safe and scope are separate issues.
This, along with an actual implementation of scope would be a
really neat thing to have, but it has 2 problems.
1. It would significantly increase language complexity (although
the return on investment would also be quite high).
2. Walter would be dead-set against it. He's said before that
implementing scope would require flow-analysis in the compiler,
which would increase the implementation complexity by a lot. On
the flipside of that, if someone ever convinces him and
flow-analysis *is* added, it opens up the door to a whole new
world of other possible enhancements.