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