scope escaping
Adam D. Ruppe
destructionator at gmail.com
Thu Feb 6 07:53:00 PST 2014
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.
More information about the Digitalmars-d
mailing list