Scope of variables

bearophile bearophileHUGS at lycos.com
Fri Jun 24 17:19:15 PDT 2011


This post is a follow-up of something I've written almost one year ago, that I have summarized here:
http://d.puremagic.com/issues/show_bug.cgi?id=5007

Feel free to ignore this post if you are busy, or if you didn't appreciate the precedent post.


In my C/D programs some common troubles (or even bugs) are caused by the scoping of variable/constant names: (1) wrongly thinking I am using a local name while I am using a name from an outer scope, or (2) using an outer scope name while I wrongly think I'm using a local name.

Outer scopes are struct/class attributes, global variables, or variabiles/constants defined in the outer function when you are working in an inner function.

Given the frequency of troubles such mistakes cause me, I don't understand why newer languages don't try to improve this situation with stricter visibility rules. (In D the visibility situation is more complex than C because there are structs/classes and there are inner functions too, so I think just copying C rules isn't enough. There are even inner classes, despite I have used them only once in D, to port some Java code to D. I think they are useful mostly to port Java code to D).

In Python3 the default is the opposite one, and it seems safer (this explanation about Python is not the whole story, but it's a good enough approximation for this discussion): in a function if you want to use a variable name that't not defined locally you have to use the "nonlocal" (from Python2 there is also "global" keyword for similar usage, but it's not good enough if you want to use inner functions):

# program#1
def foo():
    x = 10
    def bar():
        nonlocal x
        x += 1


Some examples of hiding outer names in D this (is shows just the first half of the problem):

// program#2
int i;
void foo(int i) {} // hiding global variable
struct Bar {
    int j, w;
    static int k;
    void spam(int j) { // hiding instance variable
        int w; // hiding instance variable
    }
    void baz(int k) { // hiding static variable
        void inner(int k) {} // hiding outer variable
    }
    static void red() {
        int k; // hiding static variable
    }
}
void main() {}


You can't adopt the Python solution in D. But doing the opposite is possible, the idea is disallowing hiding present in outer scopes (so they generate an error) unless you mark the variable with something, like a "local":


// program#3
int i;
void foo(local int i) {}
struct Bar {
    int j, w;
    static int k;
    void spam(int local j) {
        local int w;
    }
    void baz(local int k) {
        void inner(local int k) {}
    }
    static void red() {
        local int k;
    }
}
void main() {}


Time ago I have even thought about an @outer attribute, that is optional and it's more similar to the Python3 "nonlocal". If you add @outer at a function signature, you have to list all the names from outer scopes you will use inside the current function (and if you don't use them all it's an error again). In SPARK there is something similar, but more verbose and it's not optional (I don't remember if the Splint lint has something similar):


// program#4
int i = 1;
int j = 2;
@outer(out i, in j) void foo(int x) {
  i = x + j;
}
struct Foo {
  int x;
  @outer(in x, inout j) void bar() {
    j += x;
  }
}
void main() {}


"local" is more light to use, in syntax too, while @outer() is more heavy but it's more precise (but to make it work I think "local" can't be optional). Of the two ideas I like @outer() better. @outer is meant to avoid bugs but I think it's usable in debugging too, when you find a bug that I think is scope-related I add some @outer() to make the code more strict and make the bug stand out.

In theory with some more static introspection (like a __traits that given a function/method name, returns an array of variables used inside it (or used by functions called by the function), and how they are used, if read, written or both) it's possible to add an user-defined attribute like @outer with user code.


In bug 5007 Nick Sabalausky has shown a simpler idea:

// program#5
int globalVar;
class Foo() {
    int instanceVar;
    static int classVar;

    @explicitLookup // Name subject to change
    void bar() {

        int globalVar;   // Error
        int instanceVar; // Error
        int classVar;    // Error

        globalVar   = 1; // Error
        instanceVar = 1; // Error
        classVar    = 1; // Error

        .globalVar       = 1; // Ok
        this.instanceVar = 1; // Ok
        Foo.classVar     = 1; // Ok
    }
}

Bye,
bearophile


More information about the Digitalmars-d mailing list