scope escaping
Adam D. Ruppe
destructionator at gmail.com
Thu Feb 6 07:48:44 PST 2014
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