July 10, 2014 Proposal for design of 'scope' (Was: Re: Opportunities for D) | ||||
---|---|---|---|---|
| ||||
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. |
Copyright © 1999-2021 by the D Language Foundation