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