On 6/3/11 2:07 PM, Graydon Hoare wrote:
I like the line of reasoning; let me try phrasing in a slightly more
terse/pithy fashion:

"Alias-formation must preserve unique ownership of the referent"

So in a discussion on IRC we found a several issues with this scheme. Taking them one by one:

(1) It's important to be able to take aliases to the interior of a box, because it's how we abstract over interior and exterior types. For example, the list functions in list.rs have signatures of the form:

fn f[T](&list[T] l, ...)

But it's often desired to pass in a @list as the first parameter to these utility functions. To do this, we need to be able to call these functions as f(*l, ...) with l : @list. The alias analysis rules as stated forbid this: there could be multiple aliases to the interior of a box. However, in many cases, we can prove that the caller will keep a reference to the box for the duration of the call and therefore will keep its contents alive. So I propose relaxing the restrictions slightly; the caller can pass the interior of a box *stored in a local variable* as an alias.

(2) "alt" behaves much as a function call with alias parameters. To see why this is true, consider this code:

var x = some("foo");
alt (x) {
    case (some(?s)) {
        x = none;
        log s; // crash
    }
}

This crashes on the line marked "// crash" above. The reason is that "s" is an *alias* to the string, which has its last reference removed by the assignment "x = none;" above. Switching destructuring to use copying instead of aliasing won't work for several reasons orthogonal to this post (although I can go into them if anyone is interested).

The solution is to treat "s" the same as any other alias; in particular, it must temporarily render "x" inaccessible for the duration of the "alt" statement. We also have to mandate that lvalues used in the expression position of an "alt" statement are unique, so that rendering them inaccessible really does make it impossible to affect the lifetime of their subparts (IOW, in this example, we have to make sure there's no other way to get to the contents of "x", i.e. "s").

(3) Putting points (1) and (2) together, we have to ensure that two alias parameters passed into the same function don't, well, alias. This is so that alias parameters can be treated as unique in the callee. To see why this is necessary, consider this code:

fn f(&mutable option::t[str] x, &mutable option::t[str] y) {
    alt (x) {
        case (some(?s)) {
            y = none;
            log s; // crash
        }
    }
}

fn g() {
    var x = "foo";
    f(x, x);
}

fn h() {
    var x = "foo";
    var y = x;
    f(*x, *y);
}

Calls to g() and h() will crash at the line marked "// crash", because they both manage to perform a call to f() with the same value for both parameters x and y. It's easy to see how to statically forbid the call to f() inside g(); the two parameters clearly refer to the same local variable, so we can reject the call.

Proving the same for h() is more difficult, since h() takes advantage of the relaxed rules proposed in point (1) above. So in this case, and this case only--namely, when the interior of a box is passed to two type-compatible aliases--a dynamic check will be performed that the two boxes do not alias (note that this is a shallow check), and we can emit a warning.

If the user doesn't want the warning, he or she can copy one of the values before the call. This check allows users to pass box contents as aliases as expected, while (as far as I know anyway) preserving type safety and runtime performance.

Patrick
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to