The next iteration of scope

via Digitalmars-d digitalmars-d at puremagic.com
Mon Mar 16 10:00:11 PDT 2015


On Monday, 16 March 2015 at 15:25:45 UTC, Zach the Mystic wrote:
> If an RC'd struct is heap-allocated, but one of its members 
> points to the stack, how is it ever safe to escape it?

It isn't.

> Why shouldn't the heap variable be treated as scoped too, 
> inheriting the most restricted scope of any of its members?

See below.

> To me, the question is not how you can know that a member is 
> scoped, so much as how you could know that it *isn't* scoped, 
> i.e. that a sub-pointer was global while the parent was local. 
> I think it would require a very complicated type system:
>
> struct S {
>   T* p;
> }
>
> // note the elaborate new return signature
> T* fun(return!(S.p) S x) {
>   return x.p;
> }
>
> T* run() {
>   S s;
>   s.p = new T; // s local, s.p global
>   return fun(s);
> }
>
> The above is technically safe, but the question is whether it's 
> too complicated for the benefit. In the absence of such a 
> complicated system, the safe default is to assume a struct is 
> always as scoped as its most scoped member (i.e. transitive 
> scoping).

My old proposal treated scope as a type modifier. It would have 
made something like your example possible. But it's now a storage 
class, which means that we lose information when we go through an 
indirection.

The solution is indeed a kind of transitivity. I describe this in 
the section on multiple indirections:
http://wiki.dlang.org/User:Schuetzm/scope2#Multiple_indirections

> Your idea of `scope` members would only be valid in the absence 
> of this safe default. But even then it would be of limited 
> usefulness, because it would prevent all uses of global 
> references in those members, even if the parent was global. For 
> me, it comes down to that you can't know if anything is global 
> or local until you define an instance of it, which you can't do 
> in the struct definition.

I think there is a misunderstanding. scope members don't really 
introduce anything new. They are exactly equivalent to an 
accessor method:

struct S {
     scope int* payload;
     scope int** indirect;
}

behaves exactly the same as:

struct S {
     private int* payload_;
     ref int* payload() return {
         return payload_;
     }
}

By the rules for multiple indirections, it's not possible to do 
unsafe things with them:

void foo() {
     // reading (right hand side):

     S s;
     int* p = s.payload;  // OK, `payload` lives at least as long 
as `s`
     p = *s.indirect;     // ditto

     {
         S s2;
         p = s2.payload;  // NOT OK, we don't know how long 
`payload`
                          // really lives, so we assume it is
                          // destroyed when `s2` goes out of scope
         p = *s2.indirect;// ditto
     }

     // assignment (left hand side):

     s.payload = new int; // OK, `new int` is on the heap

     int x;
     s.payload = &x;      // NOT OK, `payload` is scope by its 
`this`
                          // which is `s`, which outlives `x`
     *s.indirect = new int*;
                          // OK: `new int*` is on the heap
     *s.indirect = &p;    // NOT OK: accessed through indirection
                          // must assume destination exists forever
                          // => cannot contain pointer to local
}

The only difference it makes whether a member is annotated as 
scope or not is when it's accessed from inside a method, because 
in this case, the compiler doesn't know anything about the 
context in which the method is called.

BUt there is indeed still some confusion on my side. It's about 
the question whether `this` should implicitly be passed as 
`scope` or not. Because if it is, scope members are probably 
useless, because they are already implied. I think I should 
remove this suggestion, because it would break too much code (in 
@system).


More information about the Digitalmars-d mailing list