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.

Reply via email to