scope escaping

Adam D. Ruppe destructionator at gmail.com
Thu Feb 6 07:47:43 PST 2014


Let's see if we can make this work in two steps: first, making 
the existing scope storage class work, and second, but 
considering making it the default.

First, let's define it. A scope reference may never escape its 
scope. This means:

0) Note that scope is irrelevant on value types. I believe it is 
also mostly irrelevant on references to immutable data (such as 
strings) since they are de facto value types. (A side effect of 
this: immutable stack data is wrong.... which is an arguable 
point, since correct enforcement of slices into it would let you 
maintain the immutable illusion. Hmm, both sides have good 
points.)

Nevertheless, while the immutable reference can be debated, scope 
definitely doesn't matter on value types. While it might be 
there, I think it should just be a no-op.

1) It or its address must never be assigned to a higher scope. 
(The compiler currently disallows rebinding scope variables, 
which I think does achieve this, but is more blunt than it needs 
to be. If we want to disable rebinding, let's do that on a 
type-by-type basis e.g. disabling postblit on a unique ptr.)

void foo() {
    int[] outerSlice;
    {
       scope int[] innerSlice = ...;
       outerSlice = innerSlice; // error
       innerSlice = innerSlice[1 .. $]; // I think this should be 
ok
    }
}

Parameters and return values are considered the same level for 
this, since the parameter and return value both belong to the 
caller. So:

int[] foo() {
    int[15] staticBuffer;
    scope int[] slice = staticBuffer[];
    return slice; // illegal, return value is one level higher 
than inner function
}

// OK, you aren't giving the caller anything they don't already 
have
scope char[] strchr(scope char[] s, char[]) { return s; }

It is acceptable to pass it to a lower scope.

int average(in int[]); // in == const scope

void foo() {
     int[15] staticBuffer;
     scope int[] slice = staticBuffer[];
     int avg = average(slice); // OK, passing to inner scope is 
fine
}


scope slice.ptr and &scope slice's return values themselves must 
be scope. Yes, scope MUST work on function return values as well 
as parameters and variables. This is an absolute necessity for 
any degree of sanity, which I'll talk about more in my next 
numbered point.


BTW I keep using slices into static buffers here because that's 
the main real-world concern we should keep in mind. A static 
buffer is a strictly-scoped owned container built right into the 
language. We know it is wrong to return a reference to stack 
data, we know why. Conversely, we have a pretty good idea about 
what *can* work with it. Scope, if we do it right, should 
statically catch misuses of static array slices while allowing 
proper uses.

So when in doubt about something, ask: does this make sense when 
referring to a static buffer slice?

2) scope must be carried along with the variable at every step of 
its life. (In this sense, it starts to look more like a type 
constructor than a storage class, but I think it is slightly 
different still.)

void foo() {
    int[] outerSlice;
    {
        int[16] staticBuffer;
        scope int[] innerSlice = staticBuffer[]; // OK
        int[] cheatingSlice = innerSlice; // uh oh, no good 
because...
        outerSlice = cheatingSlice; // ...it enables this
    }
}


A potential workaround is to require every assignment to also be 
scope.

        scope int[] cheatingSlice = innerSlice; // OK
        outerSlice = cheatingSlice; // this is still disallowed, 
so cool

It is very important that this also applies through function 
return values, since otherwise:

T identity(T)(scope T t) { return t; }

can and will escape references. Consider strchr on a static stack 
array. We do NOT want that to return a pointer to the stack 
memory after it ceases to exist.

This, that identity function should be illegal with cannot return 
scope from a non-scope function. We'll allow it by marking the 
return value as scope as well. (Again, this sounds a lot like a 
type constructor.)


3) structs are considered reference types if ANY of their members 
are reference types (unless specifically noted otherwise, see my 
following post about default and encapsulation for details). 
Thus, the scope rules may apply to them:

struct Holder {
    int[] foo;
}

Holder h;
void test(scope int[] f) {
     h.foo = f; // must be an error, f is escaping to global scope 
directly
     h = Holder(f); // this must also be an error, f is escaping 
indirectly
}

The constructed Holder inside would have to inherit the scopiness 
of f. This might be the trickiest part of getting this right 
(though it is kinda neatly solved if scope is default :) )

a) A struct constructed with a scope variable itself must be 
scope, and thus all the rules apply to it.

b) Assigning to a struct which is not scope, even if it is a 
local variable, must not be permitted.

Holder h2;
h2.foo = f; // this isn't escaping the scope, but is dropping 
scope

Just as if we had a local variable of type int[].

We may make the struct scope:

scope Holder h2;
h2.foo = f; // OK

c) Calling methods on a struct which may escape the scope is 
wrong. Ideally, `this` would always be scope... in fact, I think 
that's the best way to go. An alternative though might be to 
restrict calling of non-pure functions. Pure functions don't 
allow mutation of non-scope data in the first place, so they 
shouldn't be able to escape references.




I think that covers what I want. Note that this is not 
necessarily @safe:

struct C_Array { /* grows with malloc */ scope T* borrow() {} }

C_Array!int i;
int* b = i.borrow;
i ~= 10; // might realloc...
// leaving b dangling


So it isn't necessarily @safe. I think it *would* be @safe with 
static arrays. BTW static array slicing should return scope as 
should most user defined containers. But with user-defined types, 
@safety is still in the hands of the programmer. Reallocing with 
a non-sealed reference should always be considered @trusted.


Stand by for my next post which will discuss making it default, 
with a few more points relevant to the whole concept.


More information about the Digitalmars-d mailing list