pull/10/head
Alexis Beingessner 10 years ago committed by Manish Goregaokar
parent 70ca9a635f
commit 931728a825

@ -92,7 +92,6 @@ quite permisive with respect to other dubious operations. Rust considers it
* Deadlock * Deadlock
* Leak memory * Leak memory
* Fail to call destructors * Fail to call destructors
* Access private fields
* Overflow integers * Overflow integers
* Delete the production database * Delete the production database

@ -102,59 +102,11 @@ more than a local lint against incorrect usage of a value.
# Weird Lifetimes
Given the following code:
```rust
struct Foo;
impl Foo {
fn mutate_and_share(&mut self) -> &Self { &*self }
fn share(&self) {}
}
fn main() {
let mut foo = Foo;
let loan = foo.mutate_and_share();
foo.share();
}
```
One might expect it to compile. We call `mutate_and_share`, which mutably borrows
`foo` *temporarily*, but then returns *only* a shared reference. Therefore we
would expect `foo.share()` to succeed as `foo` shouldn't be mutably borrowed.
However when we try to compile it:
```text
<anon>:11:5: 11:8 error: cannot borrow `foo` as immutable because it is also borrowed as mutable
<anon>:11 foo.share();
^~~
<anon>:10:16: 10:19 note: previous borrow of `foo` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `foo` until the borrow ends
<anon>:10 let loan = foo.mutate_and_share();
^~~
<anon>:12:2: 12:2 note: previous borrow ends here
<anon>:8 fn main() {
<anon>:9 let mut foo = Foo;
<anon>:10 let loan = foo.mutate_and_share();
<anon>:11 foo.share();
<anon>:12 }
^
```
What happened? Well, the lifetime of `loan` is derived from a *mutable* borrow.
This makes the type system believe that `foo` is mutably borrowed as long as
`loan` exists, even though it's a shared reference. To my knowledge, this is not
a bug.
# Lifetime Elision # Lifetime Elision
In order to make common patterns more ergonomic, Rust allows lifetimes to be In order to make common patterns more ergonomic, Rust allows lifetimes to be
*elided* in function, impl, and type signatures. *elided* in function signatures.
A *lifetime position* is anywhere you can write a lifetime in a type: A *lifetime position* is anywhere you can write a lifetime in a type:
@ -336,16 +288,18 @@ In Rust, subtyping derives entirely from *lifetimes*. Since lifetimes are derive
from scopes, we can partially order them based on an *outlives* relationship. We from scopes, we can partially order them based on an *outlives* relationship. We
can even express this as a generic bound: `T: 'a` specifies that `T` *outlives* `'a`. can even express this as a generic bound: `T: 'a` specifies that `T` *outlives* `'a`.
We can then define subtyping on lifetimes in terms of lifetimes: `'a : 'b` implies We can then define subtyping on lifetimes in terms of lifetimes: if `'a : 'b`
`'a <: b` -- if `'a` outlives `'b`, then `'a` is a subtype of `'b`. This is a very ("a outlives b"), then `'a` is a subtype of `b`. This is a
large source of confusion, because a bigger scope is a *sub type* of a smaller scope. large source of confusion, because a bigger scope is a *sub type* of a smaller scope.
This does in fact make sense. The intuitive reason for this is that if you expect an This does in fact make sense. The intuitive reason for this is that if you expect an
`&'a u8`, then it's totally fine for me to hand you an `&'static u8`, in the same way `&'a u8`, then it's totally fine for me to hand you an `&'static u8` in the same way
that if you expect an Animal in Java, it's totally fine for me to hand you a Cat. that if you expect an Animal in Java, it's totally fine for me to hand you a Cat.
(Note, the subtyping relationship and typed-ness of lifetimes is a fairly arbitrary (Note, the subtyping relationship and typed-ness of lifetimes is a fairly arbitrary
construct that some disagree with. I just find that it simplifies this analysis.) construct that some disagree with. I just find that it simplifies this analysis.)
TODO: higher rank lifetime subtyping
Variance is where things get really harsh. Variance is where things get really harsh.
Variance is a property that *type constructors* have. A type constructor in Rust Variance is a property that *type constructors* have. A type constructor in Rust
@ -356,21 +310,27 @@ take a lifetime and a type.
A type constructor's *variance* is how the subtypes of its inputs affects the A type constructor's *variance* is how the subtypes of its inputs affects the
subtypes of its outputs. There are three kinds of variance: subtypes of its outputs. There are three kinds of variance:
* F is *covariant* if `T <: U` implies `F<T> <: F<U>` * F is *variant* if `T` being a subtype of `U` implies `F<T>` is a subtype of `F<U>`
* F is *contravariant* if `T <: U` implies `F<U> <: F<T>`
* F is *invariant* otherwise (no subtyping relation can be derived) * F is *invariant* otherwise (no subtyping relation can be derived)
(For those of you who are familiar with variance from other languages, what we refer
to as "just" variant is in fact *covariant*. Rust does not have contravariance.
Historically Rust did have some contravariance but it was scrapped due to poor
interactions with other features.)
Some important variances: Some important variances:
* `&` is covariant (as is *const by metaphor) * `&` is variant (as is *const by metaphor)
* `&mut` is invariant (as is *mut by metaphor) * `&mut` is invariant (as is *mut by metaphor)
* `Fn(T)` is contravariant with respect to `T` * `Fn(T) -> U` is invariant with respect to `T`, but variant with respect to `U`
* `Box`, `Vec`, and all other collections are covariant * `Box`, `Vec`, and all other collections are variant
* `UnsafeCell`, `Cell`, `RefCell`, `Mutex` and all "interior mutability" * `UnsafeCell`, `Cell`, `RefCell`, `Mutex` and all "interior mutability"
types are invariant types are invariant
To understand why these variances are correct and desirable, we will consider several To understand why these variances are correct and desirable, we will consider several
examples. We have already covered why `&` should be covariant. examples. We have already covered why `&` should be variant when introducing subtyping:
it's desirable to be able to pass longer-lived things where shorter-lived things are
needed.
To see why `&mut` should be invariant, consider the following code: To see why `&mut` should be invariant, consider the following code:
@ -391,28 +351,29 @@ fn overwrite<T: Copy>(input: &mut T, new: &mut T) {
The signature of `overwrite` is clearly valid: it takes mutable references to two values The signature of `overwrite` is clearly valid: it takes mutable references to two values
of the same type, and replaces one with the other. We have seen already that `&` is of the same type, and replaces one with the other. We have seen already that `&` is
covariant, and `'static` is a subtype of *any* `'a`, so `&'static str` is a variant, and `'static` is a subtype of *any* `'a`, so `&'static str` is a
subtype of `&'a str`. Therefore, if `&mut` was subtype of `&'a str`. Therefore, if `&mut` was
*also* covariant, then the lifetime of the `&'static str` would successfully be *also* variant, then the lifetime of the `&'static str` would successfully be
"shrunk" down to the shorter lifetime of the string, and `replace` would be "shrunk" down to the shorter lifetime of the string, and `replace` would be
called successfully. The string would subsequently be dropped, and `forever_str` called successfully. The string would subsequently be dropped, and `forever_str`
would point to freed memory when we print it! would point to freed memory when we print it!
Therefore `&mut` should be invariant. This is the general theme of covariance vs Therefore `&mut` should be invariant. This is the general theme of variance vs
invariance: if covariance would allow you to *store* a short-lived value in a invariance: if variance would allow you to *store* a short-lived value in a
longer-lived slot, then you must be invariant. longer-lived slot, then you must be invariant.
`Box` and `Vec` are interesting cases because they're covariant, but you can `Box` and `Vec` are interesting cases because they're variant, but you can
definitely store values in them! This is fine because *you can only store values definitely store values in them! This is fine because *you can only store values
in them through a mutable reference*! The mutable reference makes the whole type in them through a mutable reference*! The mutable reference makes the whole type
invariant, and therefore prevents you from getting in trouble. invariant, and therefore prevents you from getting in trouble.
Being covariant allows them to be covariant when shared immutably (so you can pass Being variant allows them to be variant when shared immutably (so you can pass
a `&Box<&'static str>` where a `&Box<&'a str>` is expected). It also allows you to a `&Box<&'static str>` where a `&Box<&'a str>` is expected). It also allows you to
forever weaken the type by moving it into a weaker slot. That is, you can do: forever weaken the type by moving it into a weaker slot. That is, you can do:
```rust ```rust
fn get_box<'a>(&'a u8) -> Box<&'a str> { fn get_box<'a>(&'a u8) -> Box<&'a str> {
// string literals are `&'static str`s
Box::new("hello") Box::new("hello")
} }
``` ```
@ -424,51 +385,67 @@ The variance of the cell types similarly follows. `&` is like an `&mut` for a
cell, because you can still store values in them through an `&`. Therefore cells cell, because you can still store values in them through an `&`. Therefore cells
must be invariant to avoid lifetime smuggling. must be invariant to avoid lifetime smuggling.
`Fn` is the most confusing case, largely because contravariance is easily the `Fn` is the most subtle case, because it has mixed variance. To see why
most confusing kind of variance, and basically never comes up. To understand it, `Fn(T) -> U` should be invariant over T, consider the following function
consider a function `len` that takes a function `F`. signature:
```rust ```rust
fn len<F>(func: F) -> usize // 'a is derived from some parent scope
where F: Fn(&'static str) -> usize fn foo(&'a str) -> usize;
{
func("hello")
}
``` ```
We require that F is a Fn that can take an `&'static str` and returns a usize. Now This signature claims that it can handle any &str that lives *at least* as long
say we have a function that can take an `&'a str` (for *some* `'a`). Such a function actually as `'a`. Now if this signature was variant with respect to &str, that would mean
accepts *more* inputs, since `&'static str` is a subtype of `&'a str`. Therefore
`len` should happily accept such a function! ```rust
fn foo(&'static str) -> usize;
```
So a `Fn(&'a str)` is a subtype of a `Fn(&'static str)` because could be provided in its place, as it would be a subtype. However this function
`&'static str` is a subtype of `&'a str`. Exactly contravariance. has a *stronger* requirement: it says that it can *only* handle `&'static str`s,
and nothing else. Therefore functions are not variant over their arguments.
The variance of `*const` and `*mut` is basically arbitrary as they're not at all To see why `Fn(T) -> U` should be *variant* over U, consider the following
type or memory safe, so their variance is determined in analogy to & and &mut function signature:
respectively.
```rust
// 'a is derived from some parent scope
fn foo(usize) -> &'a str;
```
This signature claims that it will return something that outlives `'a`. It is
therefore completely reasonable to provide
```rust
fn foo(usize) -> &'static str;
```
in its place. Therefore functions *are* variant over their return type.
`*const` has the exact same semantics as &, so variance follows. `*mut` on the
other hand can dereference to an &mut whether shared or not, so it is marked
as invariant in analogy to cells.
This is all well and good for the types the standard library provides, but This is all well and good for the types the standard library provides, but
how is variance determined for type that *you* define? A struct informally how is variance determined for type that *you* define? A struct, informally
speaking inherits the variance of its fields. If a struct `Foo` speaking, inherits the variance of its fields. If a struct `Foo`
has a generic argument `A` that is used in a field `a`, then Foo's variance has a generic argument `A` that is used in a field `a`, then Foo's variance
over `A` is exactly `a`'s variance. However this is complicated if `A` is used over `A` is exactly `a`'s variance. However this is complicated if `A` is used
in multiple fields. in multiple fields.
* If all uses of A are covariant, then Foo is covariant over A * If all uses of A are variant, then Foo is variant over A
* If all uses of A are contravariant, then Foo is contravariant over A
* Otherwise, Foo is invariant over A * Otherwise, Foo is invariant over A
```rust ```rust
struct Foo<'a, 'b, A, B, C, D, E, F, G, H> { struct Foo<'a, 'b, A, B, C, D, E, F, G, H> {
a: &'a A, // covariant over 'a and A a: &'a A, // variant over 'a and A
b: &'b mut B, // invariant over 'b and B b: &'b mut B, // invariant over 'b and B
c: *const C, // covariant over C c: *const C, // variant over C
d: *mut D, // invariant over D d: *mut D, // invariant over D
e: Vec<E>, // covariant over E e: Vec<E>, // variant over E
f: Cell<F>, // invariant over F f: Cell<F>, // invariant over F
g: G // covariant over G g: G // variant over G
h1: H // would also be covariant over H except... h1: H // would also be variant over H except...
h2: Cell<H> // invariant over H, because invariance wins h2: Cell<H> // invariant over H, because invariance wins
} }
``` ```
@ -497,8 +474,9 @@ correct variance and drop checking.
We do this using *PhantomData*, which is a special marker type. PhantomData We do this using *PhantomData*, which is a special marker type. PhantomData
consumes no space, but simulates a field of the given type for the purpose of consumes no space, but simulates a field of the given type for the purpose of
variance. This was deemed to be less error-prone than explicitly telling the static analysis. This was deemed to be less error-prone than explicitly telling
type-system the kind of variance that you want. the type-system the kind of variance that you want, while also providing other
useful information.
Iter logically contains `&'a T`, so this is exactly what we tell Iter logically contains `&'a T`, so this is exactly what we tell
the PhantomData to simulate: the PhantomData to simulate:
@ -526,16 +504,16 @@ However the one exception is with PhantomData. Given a struct like Vec:
``` ```
struct Vec<T> { struct Vec<T> {
data: *const T, // *const for covariance! data: *const T, // *const for variance!
len: usize, len: usize,
cap: usize, cap: usize,
} }
``` ```
dropck will generously determine that Vec<T> does not contain any values of dropck will generously determine that Vec<T> does not own any values of
type T. This will unfortunately allow people to construct unsound Drop type T. This will unfortunately allow people to construct unsound Drop
implementations that access data that has already been dropped. In order to implementations that access data that has already been dropped. In order to
tell dropck that we *do* own values of type T and may call destructors of that tell dropck that we *do* own values of type T, and may call destructors of that
type, we must add extra PhantomData: type, we must add extra PhantomData:
``` ```
@ -700,3 +678,56 @@ Bar is *also* live. Since IterMut is always live when `next` can be called, if
to it exist! to it exist!
# Weird Lifetimes
Given the following code:
```rust
struct Foo;
impl Foo {
fn mutate_and_share(&mut self) -> &Self { &*self }
fn share(&self) {}
}
fn main() {
let mut foo = Foo;
let loan = foo.mutate_and_share();
foo.share();
}
```
One might expect it to compile. We call `mutate_and_share`, which mutably borrows
`foo` *temporarily*, but then returns *only* a shared reference. Therefore we
would expect `foo.share()` to succeed as `foo` shouldn't be mutably borrowed.
However when we try to compile it:
```text
<anon>:11:5: 11:8 error: cannot borrow `foo` as immutable because it is also borrowed as mutable
<anon>:11 foo.share();
^~~
<anon>:10:16: 10:19 note: previous borrow of `foo` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `foo` until the borrow ends
<anon>:10 let loan = foo.mutate_and_share();
^~~
<anon>:12:2: 12:2 note: previous borrow ends here
<anon>:8 fn main() {
<anon>:9 let mut foo = Foo;
<anon>:10 let loan = foo.mutate_and_share();
<anon>:11 foo.share();
<anon>:12 }
^
```
What happened? Well, the lifetime of `loan` is derived from a *mutable* borrow.
This makes the type system believe that `foo` is mutably borrowed as long as
`loan` exists, even though it's a shared reference. This isn't a bug, although
one could argue it is a limitation of the design. In particular, to know if
the mutable part of the borrow is *really* expired we'd have to peek into
implementation details of the function. Currently, type-checking a function
does not need to inspect the bodies of any other functions or types.

Loading…
Cancel
Save