Proposal for design of 'scope' (Was: Re: Opportunities for D)

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Thu Jul 10 10:02:43 PDT 2014


Moving this to a new thread as suggested by Jacob.

On Wed, Jul 09, 2014 at 04:57:01PM -0700, H. S. Teoh via Digitalmars-d wrote:
> On Wed, Jul 09, 2014 at 03:16:37PM -0700, H. S. Teoh via Digitalmars-d wrote:
> [...]
> > 	https://issues.dlang.org/show_bug.cgi?id=13085
> [...]
> 
> Hmm, apparently, this is a long-standing known issue:
> 
> 	https://issues.dlang.org/show_bug.cgi?id=5270
> 
> Judging from this, a big missing piece of the current implementation is
> the actual enforcement of 'scope'.
> 
> So here's a first stab at refining (and extending) what 'scope' should
> be:
> 
> - 'scope' can be applied to any variable, and is part of its type. Let's
>   call this a "scoped type", and a value of this type a "scoped value".
> 
> - Every scoped type has an associated lifetime, which is basically the
>   scope in which it is declared.
> 
>    - The lifetime of a scoped variable is PART OF ITS TYPE.
> 
>    - An unscoped variable is regarded to have infinite lifetime.
> 
>    - For function parameters, this lifetime is the scope of the function
>      body.
> 
>    - For local variables, the lifetime is the containing lexical scope
>      where it is declared.
> 
> - Taking the address of a scoped value returns a scoped pointer, whose
>   lifetime is the lexical scope where the address-of operator is used.
> 
> - A scoped type can only be assigned to another scoped type of identical
>   or narrower lifetime.  Basically, the idea here is that a scoped value
>   can only have its scope narrowed, never expanded. In practice, this
>   means:
> 
>    - If a scoped type is a reference type (class or pointer or ref), it
>      can only be assigned to another scoped type whose associated
>      lifetime is equal or contained within the source value's associated
>      lifetime.
> 
>    - If a scoped type is a value type with indirections, it can only be
>      assigned to an lvalue of the same scoped type (with the same
>      associated lifetime).
> 
>    - If a scoped type is a value type with no indirections, it's freely
>      assignable to a non-scoped lvalue of compatible type.
> 
>    - A function's return type can be scoped (not sure what syntax to use
>      here, since it may clash with scope delegates).
> 
>       - The lifetime of the return value is the containing scope of the
>         function definition -- if it's a module-level function, then its
>         lifetime is infinite. If it's an inner function, then its
>         lifetime is the containing lexical scope of its definition.
>         Example:
> 
>             class C {}
>             void func() {
>                 // return type of helper is scope(C) with lifetime up to
>                 // the end of func's body.
>                 scope(C) helper() { ... }
>             }
> 
>       - Returning a value from a function is considered to be equivalent
>         to assigning the value to a variable of the return type of the
>         function. Thus:
> 
>             class C {}
>             void func() {
>                 scope(C) c1;
> 
>                 // helper's return type has lifetime == func's body
>                 scope(C) helper() {
>                     scope(C) c2;
>                     if (cond)
>                         return c1; // OK, c1's lifetime == func's body
>                     else
>                         return c2; // ILLEGAL: c2's lifetime < func's body
>                 }
>             }
> 
>       - Since a scoped return type has its lifetime as part of its type,
>         the type system ensures that scoped values never escape their
>         lifetime. For example, if we are sneaky and return a pointer to
>         an inner function, the type system will prevent leakage of the
>         scoped value:
> 
>             class C {}
>             auto func() {
>                 scope(C) c1;
> 
>                 // Return type of sneaky is scope(C) with lifetime =
>                 // body of func.
>                 scope(C) sneaky() {
>                     // This is OK, because c1's lifetime == body of
>                     // func, which is compatible with its return type.
>                     return c1;
>                 }
> 
>                 // Aha! we now we have broken scope... or have we?
>                 return &sneaky;
>             }
> 
>             void main() {
>                 // Let's see. Get a function pointer to a function that
>                 // "leaks" a scoped value...
>                 auto funcptr = func();
> 
>                 // But this doesn't compile, because the return type of
>                 // funcptr() is a scoped variable whose lifetime is
>                 // inside the body of func(), but since we're outside of
>                 // func here, the lifetime of x doesn't match the
>                 // lifetime of funcptr()'s return value, so the
>                 // following assignment is rejected as having
>                 // incompatible types:
>                 auto x = funcptr();
> 
>                 // This will actually work... but it's OK, because we
>                 // aren't actually storing the return value of
>                 // funcptr(), so the scoped value is actually not leaked
>                 // after all.
>                 funcptr();
>             }
> 
> - Aggregates:
> 
>    - It's turtles all the way down: members of scoped aggregates also
>      have scoped type, with lifetime inherited from the parent
>      aggregate. In other words, the lifetime of the aggregate is
>      transitive to the lifetime of its members. For example:
> 
>         class C {}
>         struct S {
>             C c;
>             int x;
>         }
>         int func(scope S s) {
>             // N.B. lifetime of s is func's body.
>             auto c1 = s.c; // typeof(c1) == scope(C) with lifetime = func's body
>             C d;
>             d = c1; // illegal: c1 has shorter lifetime than d.
>             return s.x; // OK, even though typeof(s.x) has lifetime =
>                         // func's body, it's a value type so we're
>                         // actually copying it to func's return value,
>                         // not returning the actual scoped int.
>         }
> 
> - Passing parameters: since unscoped values are regarded to have
>   infinite lifetime, it's OK to pass unscoped values into scoped
>   function parameters: it's a narrowing of lifetime of the original
>   value, which is allowed. (What's not allowed is expanding the lifetime
>   of a scoped value.)
> 
> I'm sure there are plenty of holes in this proposal, so destroy away.
> ;-)
> 
> 
> T
> 
> -- 
> If I were two-faced, would I be wearing this one? -- Abraham Lincoln


T

-- 
All problems are easy in retrospect.


More information about the Digitalmars-d mailing list