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