User:Quickfur/DIP scope

From D Wiki
Jump to: navigation, search
Title: DIP Proposed design for "scope" implementation
DIP: ???
Version: 1.9
Status: Draft
Created: 2014-7-10
Last Modified: 2014-7-10
Author: H. S. Teoh
Links: ...

Abstract

The goal of this DIP is to work out a concrete design for the implementation of 'scope'.

Rationale

The 'scope' keyword has been around for years, yet it is barely implemented and it's unclear just what it's supposed to mean (not to mention some loopholes that can lead to runtime memory corruption and crashes). This DIP hopes to work out concrete details on the semantics of 'scope' so that it actually has a chance of being implemented.

Description

This DIP proposes the following:

  • scope can be applied to any type in the language, and becomes part of the type. Thus, scope(int) is a distinct type from int, for example. We shall call these types scoped types. They interact with the current types in the language via a set of rules, which will be defined below.
  • Every scoped type is associated with a lifetime. For convenience, we may regard non-scoped types as a special case of scoped types, where the associated lifetime is infinite. The lifetime of a scoped type is inherited from the scope in which it is declared, and is regarded as part of its type. Thus, a scope(int) declared in one lexical scope is considered to be a different type than a scope(int) declared in a different lexical scope (even though they will, in general, be compatible with each other according to the rules described in the remainder of this DIP). Exactly what lifetime is assigned to a scoped type is defined in the following section.

Lifetimes

  • The lifetime of a local variable is from the point in enclosing lexical scope where it is declared, to the end of that scope. The lifetimes are thus ordered by declaration order, with variables declared later having a slightly shorter lifetime, to match the variables' order a destruction.
  • The lifetime of function parameters is slightly larger than the body of the function. Again, the individual lifetimes are ordered to match the parameters' order of destruction.
  • The lifetime of a module-global variable is infinite.
  • The lifetime of a scoped return value of a module-level function is infinite, whereas the lifetime of a scoped return value of a nested function is the body of the enclosing outer function.

Rules

This section defines the rules of interaction between scoped types and non-scoped types.

The underlying guiding principle of all of these rules may be termed the Law of Narrowing Lifetimes, that is, one can only retain or reduce the lifetime of a scoped type, never expand it. The one exception to this law is that value types can be copied from a type of narrower lifetime to a type of a wider lifetime, because semantically, a copy of the value is being made, hence we do not violate the lifetime of the original variable that held the value.

  • A reference to a scoped type is also a scoped type, with a lifetime equal or narrower to the lifetime of the referent. In no case shall there be a reference to a scoped type where the reference has a longer lifetime than the referent scoped type. Thus:
  • Taking the address of a scoped variable produces a scoped pointer of the corresponding type, with the same lifetime as the variable itself.
  • Assignment: here, we distinguish between scoped value types, and scoped reference types. Note that for the purposes of this DIP, we shall regard "assignment" as including the assignment operator =, as well as passing a value to a function, or returning a value from a function.
  • A scoped reference type (i.e., a class, pointer, or ref) can only be assigned to another compatible scoped type with the same lifetime or narrower. It is illegal to assign a scoped type to a type with a wider lifetime.
  • A scoped value type may be assigned to a compatible type of a wider lifetime, provided it does not contain any indirections. If it does contain indirections, then it is subject to the assignment rule of scoped reference types, that is, it can only be assigned to a compatible scoped type of equal or narrower lifetime.
  • An unscoped type (i.e., a type with infinite lifetime) may be freely assigned to a scoped type. (But the converse is not necessarily true.)
  • Aggregates: it's turtles all the way down. Members of scoped aggregate types also have the corresponding scoped types, with lifetime inherited from the lifetime of the parent aggregate.

Usage

This section gives some concrete examples of how the proposed scope system would work, in order to give a better idea of how the rules are to be interpreted and implemented. Note that the code shown here uses tentative syntax; the final syntax has yet to be worked out.

  • Lifetimes:
class C {}
C func() { // the lifetime of func's return value is infinite
    scope(C) helper() {
        return new C;  // the lifetime of helper's return value is the body of func
    }
    return new C;  // OK: new C has infinite lifetime;
                   // return value has infinite lifetime
    return helper(); // ILLEGAL: lifetime of helper's return value
                     // is shorter than lifetime of func's return value
}
void func2() {
    scope(C) c1;
    scope(C) helper() { // N.B. lifetime of helper's return value = body of func2
        scope(C) c2;
        return c1; // OK: c1's lifetime == lifetime of helper's return value
        return c2; // ILLEGAL: c2's lifetime is shorter than lifetime of
                   // helper's return value
    }
    C helper2() {     // N.B. lifetime of helper2's return value = infinite
        scope(C) c3;
        return c1;    // ILLEGAL: c1's lifetime is shorter than lifetime of
                      // helper2's return value
        return c3;    // ILLEGAL: c3's lifetime is also shorter than lifetime
                      // of return value
        return new C; // OK: new C returns value with infinite lifetime
        return new scope(C); // ILLEGAL: new scope(C) has lifetime = helper2's body
                             // which is shorter than lifetime of return value
    }
}

Implementational considerations

TBD: it would be nice if the lifetime of a scoped type can be statically determined from its lexical context, so that the lifetime annotation on the type can be erased at compile-time (i.e., if the types of x and y in a particular lexical context are both scope(int), then they should have the same lifetime). Otherwise it will have to be included in the mangling of functions, which is uglier. Also, it would be nice if lifetimes can be completely automatically inferred by the compiler, so that the user never has to write additional annotations at all.

Copyright

This document has been placed in the Public Domain.