auto merge of #10787 : nikomatsakis/rust/issue-9629-freeze-andmut, r=pnkfelix

See #9629 for details.

r? @pnkfelix
This commit is contained in:
bors
2013-12-11 05:41:18 -08:00
8 changed files with 442 additions and 206 deletions

View File

@@ -39,7 +39,7 @@ occur. It also tracks initialization sites. For each borrow and move,
it checks various basic safety conditions at this time (for example, it checks various basic safety conditions at this time (for example,
that the lifetime of the borrow doesn't exceed the lifetime of the that the lifetime of the borrow doesn't exceed the lifetime of the
value being borrowed, or that there is no move out of an `&T` value being borrowed, or that there is no move out of an `&T`
pointee). referent).
It then uses the dataflow module to propagate which of those borrows It then uses the dataflow module to propagate which of those borrows
may be in scope at each point in the procedure. A loan is considered may be in scope at each point in the procedure. A loan is considered
@@ -110,7 +110,7 @@ follows:
LOAN = (LV, LT, MQ, RESTRICTION*) LOAN = (LV, LT, MQ, RESTRICTION*)
RESTRICTION = (LV, ACTION*) RESTRICTION = (LV, ACTION*)
ACTION = MUTATE | CLAIM | FREEZE | ALIAS ACTION = MUTATE | CLAIM | FREEZE
Here the `LOAN` tuple defines the lvalue `LV` being borrowed; the Here the `LOAN` tuple defines the lvalue `LV` being borrowed; the
lifetime `LT` of that borrow; the mutability `MQ` of the borrow; and a lifetime `LT` of that borrow; the mutability `MQ` of the borrow; and a
@@ -125,7 +125,6 @@ of actions that may be restricted for the path `LV`:
- `MUTATE` means that `LV` cannot be assigned to; - `MUTATE` means that `LV` cannot be assigned to;
- `CLAIM` means that the `LV` cannot be borrowed mutably; - `CLAIM` means that the `LV` cannot be borrowed mutably;
- `FREEZE` means that the `LV` cannot be borrowed immutably; - `FREEZE` means that the `LV` cannot be borrowed immutably;
- `ALIAS` means that `LV` cannot be aliased in any way (not even `&const`).
Finally, it is never possible to move from an lvalue that appears in a Finally, it is never possible to move from an lvalue that appears in a
restriction. This implies that the "empty restriction" `(LV, [])`, restriction. This implies that the "empty restriction" `(LV, [])`,
@@ -216,6 +215,24 @@ of this rule: there are comments in the borrowck source referencing
these names, so that you can cross reference to find the actual code these names, so that you can cross reference to find the actual code
that corresponds to the formal rule. that corresponds to the formal rule.
### Invariants
I want to collect, at a high-level, the invariants the borrow checker
maintains. I will give them names and refer to them throughout the
text. Together these invariants are crucial for the overall soundness
of the system.
**Mutability requires uniqueness.** To mutate a path
**Unique mutability.** There is only one *usable* mutable path to any
given memory at any given time. This implies that when claiming memory
with an expression like `p = &mut x`, the compiler must guarantee that
the borrowed value `x` can no longer be mutated so long as `p` is
live. (This is done via restrictions, read on.)
**.**
### The `gather_loans` pass ### The `gather_loans` pass
We start with the `gather_loans` pass, which walks the AST looking for We start with the `gather_loans` pass, which walks the AST looking for
@@ -325,19 +342,19 @@ The scope of a field is the scope of the struct:
SCOPE(LV.f) = SCOPE(LV) SCOPE(LV.f) = SCOPE(LV)
The scope of a unique pointee is the scope of the pointer, since The scope of a unique referent is the scope of the pointer, since
(barring mutation or moves) the pointer will not be freed until (barring mutation or moves) the pointer will not be freed until
the pointer itself `LV` goes out of scope: the pointer itself `LV` goes out of scope:
SCOPE(*LV) = SCOPE(LV) if LV has type ~T SCOPE(*LV) = SCOPE(LV) if LV has type ~T
The scope of a managed pointee is also the scope of the pointer. This The scope of a managed referent is also the scope of the pointer. This
is a conservative approximation, since there may be other aliases fo is a conservative approximation, since there may be other aliases fo
that same managed box that would cause it to live longer: that same managed box that would cause it to live longer:
SCOPE(*LV) = SCOPE(LV) if LV has type @T or @mut T SCOPE(*LV) = SCOPE(LV) if LV has type @T or @mut T
The scope of a borrowed pointee is the scope associated with the The scope of a borrowed referent is the scope associated with the
pointer. This is a conservative approximation, since the data that pointer. This is a conservative approximation, since the data that
the pointer points at may actually live longer: the pointer points at may actually live longer:
@@ -394,7 +411,7 @@ moves occur. Conditions (2) and (3) then serve to guarantee that the
value is not mutated or moved. Note that lvalues are either value is not mutated or moved. Note that lvalues are either
(ultimately) owned by a local variable, in which case we can check (ultimately) owned by a local variable, in which case we can check
whether that local variable is ever moved in its scope, or they are whether that local variable is ever moved in its scope, or they are
owned by the pointee of an (immutable, due to condition 2) managed or owned by the referent of an (immutable, due to condition 2) managed or
borrowed pointer, in which case moves are not permitted because the borrowed pointer, in which case moves are not permitted because the
location is aliasable. location is aliasable.
@@ -493,13 +510,13 @@ frozen or aliased, we cannot allow the owner to be frozen or aliased,
since doing so indirectly freezes/aliases the field. This is the since doing so indirectly freezes/aliases the field. This is the
origin of inherited mutability. origin of inherited mutability.
### Restrictions for loans of owned pointees ### Restrictions for loans of owned referents
Because the mutability of owned pointees is inherited, restricting an Because the mutability of owned referents is inherited, restricting an
owned pointee is similar to restricting a field, in that it implies owned referent is similar to restricting a field, in that it implies
restrictions on the pointer. However, owned pointers have an important restrictions on the pointer. However, owned pointers have an important
twist: if the owner `LV` is mutated, that causes the owned pointee twist: if the owner `LV` is mutated, that causes the owned referent
`*LV` to be freed! So whenever an owned pointee `*LV` is borrowed, we `*LV` to be freed! So whenever an owned referent `*LV` is borrowed, we
must prevent the owned pointer `LV` from being mutated, which means must prevent the owned pointer `LV` from being mutated, which means
that we always add `MUTATE` and `CLAIM` to the restriction set imposed that we always add `MUTATE` and `CLAIM` to the restriction set imposed
on `LV`: on `LV`:
@@ -508,9 +525,9 @@ on `LV`:
TYPE(LV) = ~Ty TYPE(LV) = ~Ty
RESTRICTIONS(LV, LT, ACTIONS|MUTATE|CLAIM) = RS RESTRICTIONS(LV, LT, ACTIONS|MUTATE|CLAIM) = RS
### Restrictions for loans of immutable managed/borrowed pointees ### Restrictions for loans of immutable managed/borrowed referents
Immutable managed/borrowed pointees are freely aliasable, meaning that Immutable managed/borrowed referents are freely aliasable, meaning that
the compiler does not prevent you from copying the pointer. This the compiler does not prevent you from copying the pointer. This
implies that issuing restrictions is useless. We might prevent the implies that issuing restrictions is useless. We might prevent the
user from acting on `*LV` itself, but there could be another path user from acting on `*LV` itself, but there could be another path
@@ -519,8 +536,13 @@ restricting that path. Therefore, the rule for `&Ty` and `@Ty`
pointers always returns an empty set of restrictions, and it only pointers always returns an empty set of restrictions, and it only
permits restricting `MUTATE` and `CLAIM` actions: permits restricting `MUTATE` and `CLAIM` actions:
RESTRICTIONS(*LV, LT, ACTIONS) = [] // R-Deref-Imm-Managed
TYPE(LV) = @Ty
ACTIONS subset of [MUTATE, CLAIM]
RESTRICTIONS(*LV, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed RESTRICTIONS(*LV, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed
TYPE(LV) = &Ty or @Ty TYPE(LV) = &LT' Ty
LT <= LT' // (1)
ACTIONS subset of [MUTATE, CLAIM] ACTIONS subset of [MUTATE, CLAIM]
The reason that we can restrict `MUTATE` and `CLAIM` actions even The reason that we can restrict `MUTATE` and `CLAIM` actions even
@@ -528,16 +550,95 @@ without a restrictions list is that it is never legal to mutate nor to
borrow mutably the contents of a `&Ty` or `@Ty` pointer. In other borrow mutably the contents of a `&Ty` or `@Ty` pointer. In other
words, those restrictions are already inherent in the type. words, those restrictions are already inherent in the type.
Typically, this limitation is not an issue, because restrictions other Clause (1) in the rule for `&Ty` deserves mention. Here I
than `MUTATE` or `CLAIM` typically arise due to `&mut` borrow, and as specify that the lifetime of the loan must be less than the lifetime
we said, that is already illegal for `*LV`. However, there is one case of the `&Ty` pointer. In simple cases, this clause is redundant, since
where we can be asked to enforce an `ALIAS` restriction on `*LV`, the `LIFETIME()` function will already enforce the required rule:
which is when you have a type like `&&mut T`. In such cases we will
report an error because we cannot enforce a lack of aliases on a `&Ty`
or `@Ty` type. That case is described in more detail in the section on
mutable borrowed pointers.
### Restrictions for loans of const aliasable pointees fn foo(point: &'a Point) -> &'static f32 {
&point.x // Error
}
The above example fails to compile both because of clause (1) above
but also by the basic `LIFETIME()` check. However, in more advanced
examples involving multiple nested pointers, clause (1) is needed:
fn foo(point: &'a &'b mut Point) -> &'b f32 {
&point.x // Error
}
The `LIFETIME` rule here would accept `'b` because, in fact, the
*memory is* guaranteed to remain valid (i.e., not be freed) for the
lifetime `'b`, since the `&mut` pointer is valid for `'b`. However, we
are returning an immutable reference, so we need the memory to be both
valid and immutable. Even though `point.x` is referenced by an `&mut`
pointer, it can still be considered immutable so long as that `&mut`
pointer is found in an aliased location. That means the memory is
guaranteed to be *immutable* for the lifetime of the `&` pointer,
which is only `'a`, not `'b`. Hence this example yields an error.
As a final twist, consider the case of two nested *immutable*
pointers, rather than a mutable pointer within an immutable one:
fn foo(point: &'a &'b Point) -> &'b f32 {
&point.x // OK
}
This function is legal. The reason for this is that the inner pointer
(`*point : &'b Point`) is enough to guarantee the memory is immutable
and valid for the lifetime `'b`. This is reflected in
`RESTRICTIONS()` by the fact that we do not recurse (i.e., we impose
no restrictions on `LV`, which in this particular case is the pointer
`point : &'a &'b Point`).
#### Why both `LIFETIME()` and `RESTRICTIONS()`?
Given the previous text, it might seem that `LIFETIME` and
`RESTRICTIONS` should be folded together into one check, but there is
a reason that they are separated. They answer separate concerns.
The rules pertaining to `LIFETIME` exist to ensure that we don't
create a borrowed pointer that outlives the memory it points at. So
`LIFETIME` prevents a function like this:
fn get_1<'a>() -> &'a int {
let x = 1;
&x
}
Here we would be returning a pointer into the stack. Clearly bad.
However, the `RESTRICTIONS` rules are more concerned with how memory
is used. The example above doesn't generate an error according to
`RESTRICTIONS` because, for local variables, we don't require that the
loan lifetime be a subset of the local variable lifetime. The idea
here is that we *can* guarantee that `x` is not (e.g.) mutated for the
lifetime `'a`, even though `'a` exceeds the function body and thus
involves unknown code in the caller -- after all, `x` ceases to exist
after we return and hence the remaining code in `'a` cannot possibly
mutate it. This distinction is important for type checking functions
like this one:
fn inc_and_get<'a>(p: &'a mut Point) -> &'a int {
p.x += 1;
&p.x
}
In this case, we take in a `&mut` and return a frozen borrowed pointer
with the same lifetime. So long as the lifetime of the returned value
doesn't exceed the lifetime of the `&mut` we receive as input, this is
fine, though it may seem surprising at first (it surprised me when I
first worked it through). After all, we're guaranteeing that `*p`
won't be mutated for the lifetime `'a`, even though we can't "see" the
entirety of the code during that lifetime, since some of it occurs in
our caller. But we *do* know that nobody can mutate `*p` except
through `p`. So if we don't mutate `*p` and we don't return `p`, then
we know that the right to mutate `*p` has been lost to our caller --
in terms of capability, the caller passed in the ability to mutate
`*p`, and we never gave it back. (Note that we can't return `p` while
`*p` is borrowed since that would be a move of `p`, as `&mut` pointers
are affine.)
### Restrictions for loans of const aliasable referents
Freeze pointers are read-only. There may be `&mut` or `&` aliases, and Freeze pointers are read-only. There may be `&mut` or `&` aliases, and
we can not prevent *anything* but moves in that case. So the we can not prevent *anything* but moves in that case. So the
@@ -549,52 +650,32 @@ result.
RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Freeze-Borrowed RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Freeze-Borrowed
TYPE(LV) = &const Ty or @const Ty TYPE(LV) = &const Ty or @const Ty
### Restrictions for loans of mutable borrowed pointees ### Restrictions for loans of mutable borrowed referents
Borrowing mutable borrowed pointees is a bit subtle because we permit Mutable borrowed pointers are guaranteed to be the only way to mutate
users to freeze or claim `&mut` pointees. To see what I mean, consider this their referent. This permits us to take greater license with them; for
(perfectly safe) code example: example, the referent can be frozen simply be ensuring that we do not
use the original pointer to perform mutate. Similarly, we can allow
the referent to be claimed, so long as the original pointer is unused
while the new claimant is live.
fn foo(t0: &mut T, op: fn(&T)) { The rule for mutable borrowed pointers is as follows:
let t1: &T = &*t0; // (1)
op(t1);
}
In the borrow marked `(1)`, the data at `*t0` is *frozen* as part of a RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed
re-borrow. Therefore, for the lifetime of `t1`, `*t0` must not be
mutated. This is the same basic idea as when we freeze a mutable local
variable, but unlike in that case `t0` is a *pointer* to the data, and
thus we must enforce some subtle restrictions in order to guarantee
soundness.
Intuitively, we must ensure that `*t0` is the only *mutable* path to
reach the memory that was frozen. The reason that we are so concerned
with *mutable* paths is that those are the paths through which the
user could mutate the data that was frozen and hence invalidate the
`t1` pointer. Note that const aliases to `*t0` are acceptable (and in
fact we can't prevent them without unacceptable performance cost, more
on that later) because
There are two rules governing `&mut` pointers, but we'll begin with
the first. This rule governs cases where we are attempting to prevent
an `&mut` pointee from being mutated, claimed, or frozen, as occurs
whenever the `&mut` pointee `*LV` is reborrowed as mutable or
immutable:
RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed-1
TYPE(LV) = &LT' mut Ty TYPE(LV) = &LT' mut Ty
LT <= LT' // (1) LT <= LT' // (1)
RESTRICTIONS(LV, LT, MUTATE|CLAIM|ALIAS) = RS // (2) RESTRICTIONS(LV, LT, ACTIONS) = RS // (2)
There are two interesting parts to this rule: Let's examine the two numbered clauses:
1. The lifetime of the loan (`LT`) cannot exceed the lifetime of the Clause (1) specifies that the lifetime of the loan (`LT`) cannot
`&mut` pointer (`LT'`). The reason for this is that the `&mut` exceed the lifetime of the `&mut` pointer (`LT'`). The reason for this
pointer is guaranteed to be the only legal way to mutate its is that the `&mut` pointer is guaranteed to be the only legal way to
pointee -- but only for the lifetime `LT'`. After that lifetime, mutate its referent -- but only for the lifetime `LT'`. After that
the loan on the pointee expires and hence the data may be modified lifetime, the loan on the referent expires and hence the data may be
by its owner again. This implies that we are only able to guarantee that modified by its owner again. This implies that we are only able to
the pointee will not be modified or aliased for a maximum of `LT'`. guarantee that the referent will not be modified or aliased for a
maximum of `LT'`.
Here is a concrete example of a bug this rule prevents: Here is a concrete example of a bug this rule prevents:
@@ -612,29 +693,54 @@ There are two interesting parts to this rule:
*z += 1; // ...and yet they would be, but for clause 1. | *z += 1; // ...and yet they would be, but for clause 1. |
} <---------------------------------------------------------+ } <---------------------------------------------------------+
2. The final line recursively requires that the `&mut` *pointer* `LV` Clause (2) propagates the restrictions on the referent to the pointer
be restricted from being mutated, claimed, or aliased (not just the itself. This is the same as with an owned pointer, though the
pointee). The goal of these restrictions is to ensure that, not reasoning is mildly different. The basic goal in all cases is to
considering the pointer that will result from this borrow, `LV` prevent the user from establishing another route to the same data. To
remains the *sole pointer with mutable access* to `*LV`. see what I mean, let's examine various cases of what can go wrong and
show how it is prevented.
Restrictions against claims are necessary because if the pointer in **Example danger 1: Moving the base pointer.** One of the simplest
`LV` were to be somehow copied or moved to a different location, ways to violate the rules is to move the base pointer to a new name
then the restriction issued for `*LV` would not apply to the new and access it via that new name, thus bypassing the restrictions on
location. Note that because `&mut` values are non-copyable, a the old name. Here is an example:
simple attempt to move the base pointer will fail due to the
(implicit) restriction against moves:
// src/test/compile-fail/borrowck-move-mut-base-ptr.rs // src/test/compile-fail/borrowck-move-mut-base-ptr.rs
fn foo(t0: &mut int) { fn foo(t0: &mut int) {
let p: &int = &*t0; // Freezes `*t0` let p: &int = &*t0; // Freezes `*t0`
let t1 = t0; //~ ERROR cannot move out of `t0` let t1 = t0; //~ ERROR cannot move out of `t0`
*t1 = 22; *t1 = 22; // OK, not a write through `*t0`
} }
However, the additional restrictions against claims mean that even Remember that `&mut` pointers are linear, and hence `let t1 = t0` is a
a clever attempt to use a swap to circumvent the type system will move of `t0` -- or would be, if it were legal. Instead, we get an
encounter an error: error, because clause (2) imposes restrictions on `LV` (`t0`, here),
and any restrictions on a path make it impossible to move from that
path.
**Example danger 2: Claiming the base pointer.** Another possible
danger is to mutably borrow the base path. This can lead to two bad
scenarios. The most obvious is that the mutable borrow itself becomes
another path to access the same data, as shown here:
// src/test/compile-fail/borrowck-mut-borrow-of-mut-base-ptr.rs
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &int = &*t0; // Freezes `*t0`
let mut t2 = &mut t0; //~ ERROR cannot borrow `t0`
**t2 += 1; // Mutates `*t0`
}
In this example, `**t2` is the same memory as `*t0`. Because `t2` is
an `&mut` pointer, `**t2` is a unique path and hence it would be
possible to mutate `**t2` even though that memory was supposed to be
frozen by the creation of `p`. However, an error is reported -- the
reason is that the freeze `&*t0` will restrict claims and mutation
against `*t0` which, by clause 2, in turn prevents claims and mutation
of `t0`. Hence the claim `&mut t0` is illegal.
Another danger with an `&mut` pointer is that we could swap the `t0`
value away to create a new path:
// src/test/compile-fail/borrowck-swap-mut-base-ptr.rs // src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
fn foo<'a>(mut t0: &'a mut int, fn foo<'a>(mut t0: &'a mut int,
@@ -644,90 +750,94 @@ There are two interesting parts to this rule:
*t1 = 22; *t1 = 22;
} }
The restriction against *aliasing* (and, in turn, freezing) is This is illegal for the same reason as above. Note that if we added
necessary because, if an alias were of `LV` were to be produced, back a swap operator -- as we used to have -- we would want to be very
then `LV` would no longer be the sole path to access the `&mut` careful to ensure this example is still illegal.
pointee. Since we are only issuing restrictions against `*LV`,
these other aliases would be unrestricted, and the result would be
unsound. For example:
// src/test/compile-fail/borrowck-alias-mut-base-ptr.rs **Example danger 3: Freeze the base pointer.** In the case where the
fn foo(t0: &mut int) { referent is claimed, even freezing the base pointer can be dangerous,
as shown in the following example:
// src/test/compile-fail/borrowck-borrow-of-mut-base-ptr.rs
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &mut int = &mut *t0; // Claims `*t0`
let mut t2 = &t0; //~ ERROR cannot borrow `t0`
let q: &int = &*t2; // Freezes `*t0` but not through `*p`
*p += 1; // violates type of `*q`
}
Here the problem is that `*t0` is claimed by `p`, and hence `p` wants
to be the controlling pointer through which mutation or freezes occur.
But `t2` would -- if it were legal -- have the type `& &mut int`, and
hence would be a mutable pointer in an aliasable location, which is
considered frozen (since no one can write to `**t2` as it is not a
unique path). Therefore, we could reasonably create a frozen `&int`
pointer pointing at `*t0` that coexists with the mutable pointer `p`,
which is clearly unsound.
However, it is not always unsafe to freeze the base pointer. In
particular, if the referent is frozen, there is no harm in it:
// src/test/run-pass/borrowck-borrow-of-mut-base-ptr-safe.rs
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &int = &*t0; // Freezes `*t0` let p: &int = &*t0; // Freezes `*t0`
let q: &const &mut int = &const t0; //~ ERROR cannot borrow `t0` let mut t2 = &t0;
**q = 22; let q: &int = &*t2; // Freezes `*t0`, but that's ok...
let r: &int = &*t0; // ...after all, could do same thing directly.
} }
The current rules could use some correction: In this case, creating the alias `t2` of `t0` is safe because the only
thing `t2` can be used for is to further freeze `*t0`, which is
already frozen. In particular, we cannot assign to `*t0` through the
new alias `t2`, as demonstrated in this test case:
1. Issue #10520. Now that the swap operator has been removed, I do not // src/test/run-pass/borrowck-borrow-mut-base-ptr-in-aliasable-loc.rs
believe the restriction against mutating `LV` is needed, and in fn foo(t0: & &mut int) {
fact it prevents some useful patterns. For example, the following let t1 = t0;
function will fail to compile: let p: &int = &**t0;
**t1 = 22; //~ ERROR cannot assign
fn mut_shift_ref<'a,T>(x: &mut &'a mut [T]) -> &'a mut T {
// `mut_split_at` will restrict mutation against *x:
let (head, tail) = (*x).mut_split_at(1);
// Hence mutating `*x` yields an error here:
*x = tail;
&mut head[0]
} }
Note that this function -- which adjusts the slice `*x` in place so This distinction is reflected in the rules. When doing an `&mut`
that it no longer contains the head element and then returns a borrow -- as in the first example -- the set `ACTIONS` will be
pointer to that element separately -- is perfectly valid. It is `CLAIM|MUTATE|FREEZE`, because claiming the referent implies that it
currently implemented using unsafe code. I believe that now that cannot be claimed, mutated, or frozen by anyone else. These
the swap operator is removed from the language, we could liberalize restrictions are propagated back to the base path and hence the base
the rules and make this function be accepted normally. The idea path is considered unfreezable.
would be to have the assignment to `*x` kill the loans of `*x` and
its subpaths -- after all, those subpaths are no longer accessible
through `*x`, since it has been overwritten with a new value. Thus
those subpaths are only accessible through prior existing borrows
of `*x`, if any. The danger of the *swap* operator was that it
allowed `*x` to be mutated without making the subpaths of `*x`
inaccessible: worse, they became accessible through a new path (I
suppose that we could support swap, too, if needed, by moving the
loans over to the new path).
Note: the `swap()` function doesn't pose the same danger as the In contrast, when the referent is merely frozen -- as in the second
swap operator because it requires taking `&mut` refs to invoke it. example -- the set `ACTIONS` will be `CLAIM|MUTATE`, because freezing
the referent implies that it cannot be claimed or mutated but permits
others to freeze. Hence when these restrictions are propagated back to
the base path, it will still be considered freezable.
2. Issue #9629. The current rules correctly prohibit `&mut` pointees
from being assigned unless they are in a unique location. However,
we *also* prohibit `&mut` pointees from being frozen. This prevents
compositional patterns, like this one:
struct BorrowedMap<'a> {
map: &'a mut HashMap
}
If we have a pointer `x:&BorrowedMap`, we can't freeze `x.map`, **FIXME #10520: Restrictions against mutating the base pointer.** When
and hence can't call `find` etc on it. But that's silly, since an `&mut` pointer is frozen or claimed, we currently pass along the
fact that the `&mut` exists in frozen data implies that it restriction against MUTATE to the base pointer. I do not believe this
will not be mutable by anyone. For example, this program nets an restriction is needed. It dates from the days when we had a way to
error: mutate that preserved the value being mutated (i.e., swap). Nowadays
the only form of mutation is assignment, which destroys the pointer
being mutated -- therefore, a mutation cannot create a new path to the
same data. Rather, it removes an existing path. This implies that not
only can we permit mutation, we can have mutation kill restrictions in
the dataflow sense.
fn main() { **WARNING:** We do not currently have `const` borrows in the
let a = &mut 2; language. If they are added back in, we must ensure that they are
let b = &a; consistent with all of these examples. The crucial question will be
*a += 1; // ERROR: cannot assign to `*a` because it is borrowed what sorts of actions are permitted with a `&const &mut` pointer. I
} would suggest that an `&mut` referent found in an `&const` location be
prohibited from both freezes and claims. This would avoid the need to
prevent `const` borrows of the base pointer when the referent is
borrowed.
(Naturally `&mut` reborrows from an `&&mut` pointee should be illegal.) ### Restrictions for loans of mutable managed referents
The second rule for `&mut` handles the case where we are not adding With `@mut` referents, we don't make any static guarantees. But as a
any restrictions (beyond the default of "no move"):
RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Mut-Borrowed-2
TYPE(LV) = &mut Ty
Moving from an `&mut` pointee is never legal, so no special
restrictions are needed. This rule is used for `&const` borrows.
### Restrictions for loans of mutable managed pointees
With `@mut` pointees, we don't make any static guarantees. But as a
convenience, we still register a restriction against `*LV`, because convenience, we still register a restriction against `*LV`, because
that way if we *can* find a simple static error, we will: that way if we *can* find a simple static error, we will:
@@ -891,11 +1001,6 @@ computed for that program point.
While writing up these docs, I encountered some rules I believe to be While writing up these docs, I encountered some rules I believe to be
stricter than necessary: stricter than necessary:
- I think the restriction against mutating `&mut` pointers found in an
aliasable location is unnecessary. They cannot be reborrowed, to be sure,
so it should be safe to mutate them. Lifting this might cause some common
cases (`&mut int`) to work just fine, but might lead to further confusion
in other cases, so maybe it's best to leave it as is.
- I think restricting the `&mut` LV against moves and `ALIAS` is sufficient, - I think restricting the `&mut` LV against moves and `ALIAS` is sufficient,
`MUTATE` and `CLAIM` are overkill. `MUTATE` was necessary when swap was `MUTATE` and `CLAIM` are overkill. `MUTATE` was necessary when swap was
a built-in operator, but as it is not, it is implied by `CLAIM`, a built-in operator, but as it is not, it is implied by `CLAIM`,

View File

@@ -114,10 +114,26 @@ impl<'self> RestrictionsContext<'self> {
} }
mc::cat_copied_upvar(..) | // FIXME(#2152) allow mutation of upvars mc::cat_copied_upvar(..) | // FIXME(#2152) allow mutation of upvars
mc::cat_static_item(..) | mc::cat_static_item(..) => {
mc::cat_deref(_, _, mc::region_ptr(MutImmutable, _)) | Safe
mc::cat_deref(_, _, mc::gc_ptr(MutImmutable)) => { }
mc::cat_deref(cmt_base, _, mc::region_ptr(MutImmutable, lt)) => {
// R-Deref-Imm-Borrowed // R-Deref-Imm-Borrowed
if !self.bccx.is_subregion_of(self.loan_region, lt) {
self.bccx.report(
BckError {
span: self.span,
cmt: cmt_base,
code: err_borrowed_pointer_too_short(
self.loan_region, lt, restrictions)});
return Safe;
}
Safe
}
mc::cat_deref(_, _, mc::gc_ptr(MutImmutable)) => {
// R-Deref-Imm-Managed
Safe Safe
} }
@@ -170,30 +186,19 @@ impl<'self> RestrictionsContext<'self> {
} }
mc::cat_deref(cmt_base, _, pk @ mc::region_ptr(MutMutable, lt)) => { mc::cat_deref(cmt_base, _, pk @ mc::region_ptr(MutMutable, lt)) => {
// Because an `&mut` pointer does not inherit its // R-Deref-Mut-Borrowed
// mutability, we can only prevent mutation or prevent
// freezing if it is not aliased. Therefore, in such
// cases we restrict aliasing on `cmt_base`.
if restrictions != RESTR_EMPTY {
if !self.bccx.is_subregion_of(self.loan_region, lt) { if !self.bccx.is_subregion_of(self.loan_region, lt) {
self.bccx.report( self.bccx.report(
BckError { BckError {
span: self.span, span: self.span,
cmt: cmt_base, cmt: cmt_base,
code: err_mut_pointer_too_short( code: err_borrowed_pointer_too_short(
self.loan_region, lt, restrictions)}); self.loan_region, lt, restrictions)});
return Safe; return Safe;
} }
// R-Deref-Mut-Borrowed-1 let result = self.restrict(cmt_base, restrictions);
let result = self.restrict(
cmt_base,
RESTR_ALIAS | RESTR_MUTATE | RESTR_CLAIM);
self.extend(result, cmt.mutbl, LpDeref(pk), restrictions) self.extend(result, cmt.mutbl, LpDeref(pk), restrictions)
} else {
// R-Deref-Mut-Borrowed-2
Safe
}
} }
mc::cat_deref(_, _, mc::unsafe_ptr(..)) => { mc::cat_deref(_, _, mc::unsafe_ptr(..)) => {

View File

@@ -444,7 +444,8 @@ pub enum bckerr_code {
err_out_of_root_scope(ty::Region, ty::Region), // superscope, subscope err_out_of_root_scope(ty::Region, ty::Region), // superscope, subscope
err_out_of_scope(ty::Region, ty::Region), // superscope, subscope err_out_of_scope(ty::Region, ty::Region), // superscope, subscope
err_freeze_aliasable_const, err_freeze_aliasable_const,
err_mut_pointer_too_short(ty::Region, ty::Region, RestrictionSet), // loan, ptr err_borrowed_pointer_too_short(
ty::Region, ty::Region, RestrictionSet), // loan, ptr
} }
// Combination of an error code and the categorization of the expression // Combination of an error code and the categorization of the expression
@@ -670,21 +671,15 @@ impl BorrowckCtxt {
// supposed to be going away. // supposed to be going away.
format!("unsafe borrow of aliasable, const value") format!("unsafe borrow of aliasable, const value")
} }
err_mut_pointer_too_short(_, _, r) => { err_borrowed_pointer_too_short(..) => {
let descr = match opt_loan_path(err.cmt) { let descr = match opt_loan_path(err.cmt) {
Some(lp) => format!("`{}`", self.loan_path_to_str(lp)), Some(lp) => format!("`{}`", self.loan_path_to_str(lp)),
None => ~"`&mut` pointer" None => self.cmt_to_str(err.cmt),
}; };
let tag = if r.intersects(RESTR_ALIAS) { format!("lifetime of {} is too short to guarantee \
"its contents are unique" its contents can be safely reborrowed",
} else { descr)
"its contents are not otherwise mutable"
};
format!("lifetime of {} is too short to guarantee {} \
so they can be safely reborrowed",
descr, tag)
} }
} }
} }
@@ -761,10 +756,10 @@ impl BorrowckCtxt {
""); "");
} }
err_mut_pointer_too_short(loan_scope, ptr_scope, _) => { err_borrowed_pointer_too_short(loan_scope, ptr_scope, _) => {
let descr = match opt_loan_path(err.cmt) { let descr = match opt_loan_path(err.cmt) {
Some(lp) => format!("`{}`", self.loan_path_to_str(lp)), Some(lp) => format!("`{}`", self.loan_path_to_str(lp)),
None => ~"`&mut` pointer" None => self.cmt_to_str(err.cmt),
}; };
note_and_explain_region( note_and_explain_region(
self.tcx, self.tcx,

View File

@@ -1,3 +1,13 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Test that attempt to reborrow an `&mut` pointer in an aliasable // Test that attempt to reborrow an `&mut` pointer in an aliasable
// location yields an error. // location yields an error.
// //
@@ -7,7 +17,7 @@ use std::util::swap;
fn foo(t0: & &mut int) { fn foo(t0: & &mut int) {
let t1 = t0; let t1 = t0;
let p: &int = &**t0; //~ ERROR cannot borrow an `&mut` in a `&` pointer let p: &int = &**t0;
**t1 = 22; //~ ERROR cannot assign **t1 = 22; //~ ERROR cannot assign
} }

View File

@@ -0,0 +1,27 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Test that attempt to freeze an `&mut` pointer while referent is
// claimed yields an error.
//
// Example from src/middle/borrowck/doc.rs
use std::util::swap;
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &mut int = &mut *t0; // Claims `*t0`
let mut t2 = &t0; //~ ERROR cannot borrow `t0`
let q: &int = &**t2; // Freezes `*t0` but not through `*p`
*p += 1; // violates type of `*q`
}
fn main() {
}

View File

@@ -0,0 +1,33 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Test that attempt to mutably borrow `&mut` pointer while pointee is
// borrowed yields an error.
//
// Example from src/middle/borrowck/doc.rs
use std::util::swap;
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &int = &*t0; // Freezes `*t0`
let mut t2 = &mut t0; //~ ERROR cannot borrow `t0`
**t2 += 1; // Mutates `*t0`
}
fn bar<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &mut int = &mut *t0; // Claims `*t0`
let mut t2 = &mut t0; //~ ERROR cannot borrow `t0`
**t2 += 1; // Mutates `*t0` but not through `*p`
}
fn main() {
}

View File

@@ -0,0 +1,25 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Test that freezing an `&mut` pointer while referent is
// frozen is legal.
//
// Example from src/middle/borrowck/doc.rs
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &int = &*t0; // Freezes `*t0`
let mut t2 = &t0;
let q: &int = &**t2; // Freezes `*t0`, but that's ok...
let r: &int = &*t0; // ...after all, could do same thing directly.
}
pub fn main() {
}

View File

@@ -0,0 +1,36 @@
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Test that a `&mut` inside of an `&` is freezable.
struct MutSlice<'a, T> {
data: &'a mut [T]
}
fn get<'a, T>(ms: &'a MutSlice<'a, T>, index: uint) -> &'a T {
&ms.data[index]
}
pub fn main() {
let mut data = [1, 2, 3];
{
let slice = MutSlice { data: data };
slice.data[0] += 4;
let index0 = get(&slice, 0);
let index1 = get(&slice, 1);
let index2 = get(&slice, 2);
assert_eq!(*index0, 5);
assert_eq!(*index1, 2);
assert_eq!(*index2, 3);
}
assert_eq!(data[0], 5);
assert_eq!(data[1], 2);
assert_eq!(data[2], 3);
}