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:
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.