Skip to content

Commit a1f6bbc

Browse files
committed
niko fixes
1 parent d2e802b commit a1f6bbc

File tree

2 files changed

+126
-96
lines changed

2 files changed

+126
-96
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ quite permisive with respect to other dubious operations. Rust considers it
9292
* Deadlock
9393
* Leak memory
9494
* Fail to call destructors
95-
* Access private fields
9695
* Overflow integers
9796
* Delete the production database
9897

lifetimes.md

Lines changed: 126 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -102,59 +102,11 @@ more than a local lint against incorrect usage of a value.
102102

103103

104104

105-
# Weird Lifetimes
106-
107-
Given the following code:
108-
109-
```rust
110-
struct Foo;
111-
112-
impl Foo {
113-
fn mutate_and_share(&mut self) -> &Self { &*self }
114-
fn share(&self) {}
115-
}
116-
117-
fn main() {
118-
let mut foo = Foo;
119-
let loan = foo.mutate_and_share();
120-
foo.share();
121-
}
122-
```
123-
124-
One might expect it to compile. We call `mutate_and_share`, which mutably borrows
125-
`foo` *temporarily*, but then returns *only* a shared reference. Therefore we
126-
would expect `foo.share()` to succeed as `foo` shouldn't be mutably borrowed.
127-
128-
However when we try to compile it:
129-
130-
```text
131-
<anon>:11:5: 11:8 error: cannot borrow `foo` as immutable because it is also borrowed as mutable
132-
<anon>:11 foo.share();
133-
^~~
134-
<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
135-
<anon>:10 let loan = foo.mutate_and_share();
136-
^~~
137-
<anon>:12:2: 12:2 note: previous borrow ends here
138-
<anon>:8 fn main() {
139-
<anon>:9 let mut foo = Foo;
140-
<anon>:10 let loan = foo.mutate_and_share();
141-
<anon>:11 foo.share();
142-
<anon>:12 }
143-
^
144-
```
145-
146-
What happened? Well, the lifetime of `loan` is derived from a *mutable* borrow.
147-
This makes the type system believe that `foo` is mutably borrowed as long as
148-
`loan` exists, even though it's a shared reference. To my knowledge, this is not
149-
a bug.
150-
151-
152-
153105

154106
# Lifetime Elision
155107

156108
In order to make common patterns more ergonomic, Rust allows lifetimes to be
157-
*elided* in function, impl, and type signatures.
109+
*elided* in function signatures.
158110

159111
A *lifetime position* is anywhere you can write a lifetime in a type:
160112

@@ -336,16 +288,18 @@ In Rust, subtyping derives entirely from *lifetimes*. Since lifetimes are derive
336288
from scopes, we can partially order them based on an *outlives* relationship. We
337289
can even express this as a generic bound: `T: 'a` specifies that `T` *outlives* `'a`.
338290

339-
We can then define subtyping on lifetimes in terms of lifetimes: `'a : 'b` implies
340-
`'a <: b` -- if `'a` outlives `'b`, then `'a` is a subtype of `'b`. This is a very
291+
We can then define subtyping on lifetimes in terms of lifetimes: if `'a : 'b`
292+
("a outlives b"), then `'a` is a subtype of `b`. This is a
341293
large source of confusion, because a bigger scope is a *sub type* of a smaller scope.
342294
This does in fact make sense. The intuitive reason for this is that if you expect an
343-
`&'a u8`, then it's totally fine for me to hand you an `&'static u8`, in the same way
295+
`&'a u8`, then it's totally fine for me to hand you an `&'static u8` in the same way
344296
that if you expect an Animal in Java, it's totally fine for me to hand you a Cat.
345297

346298
(Note, the subtyping relationship and typed-ness of lifetimes is a fairly arbitrary
347299
construct that some disagree with. I just find that it simplifies this analysis.)
348300

301+
TODO: higher rank lifetime subtyping
302+
349303
Variance is where things get really harsh.
350304

351305
Variance is a property that *type constructors* have. A type constructor in Rust
@@ -356,21 +310,27 @@ take a lifetime and a type.
356310
A type constructor's *variance* is how the subtypes of its inputs affects the
357311
subtypes of its outputs. There are three kinds of variance:
358312

359-
* F is *covariant* if `T <: U` implies `F<T> <: F<U>`
360-
* F is *contravariant* if `T <: U` implies `F<U> <: F<T>`
313+
* F is *variant* if `T` being a subtype of `U` implies `F<T>` is a subtype of `F<U>`
361314
* F is *invariant* otherwise (no subtyping relation can be derived)
362315

316+
(For those of you who are familiar with variance from other languages, what we refer
317+
to as "just" variant is in fact *covariant*. Rust does not have contravariance.
318+
Historically Rust did have some contravariance but it was scrapped due to poor
319+
interactions with other features.)
320+
363321
Some important variances:
364322

365-
* `&` is covariant (as is *const by metaphor)
323+
* `&` is variant (as is *const by metaphor)
366324
* `&mut` is invariant (as is *mut by metaphor)
367-
* `Fn(T)` is contravariant with respect to `T`
368-
* `Box`, `Vec`, and all other collections are covariant
325+
* `Fn(T) -> U` is invariant with respect to `T`, but variant with respect to `U`
326+
* `Box`, `Vec`, and all other collections are variant
369327
* `UnsafeCell`, `Cell`, `RefCell`, `Mutex` and all "interior mutability"
370328
types are invariant
371329

372330
To understand why these variances are correct and desirable, we will consider several
373-
examples. We have already covered why `&` should be covariant.
331+
examples. We have already covered why `&` should be variant when introducing subtyping:
332+
it's desirable to be able to pass longer-lived things where shorter-lived things are
333+
needed.
374334

375335
To see why `&mut` should be invariant, consider the following code:
376336

@@ -391,28 +351,29 @@ fn overwrite<T: Copy>(input: &mut T, new: &mut T) {
391351

392352
The signature of `overwrite` is clearly valid: it takes mutable references to two values
393353
of the same type, and replaces one with the other. We have seen already that `&` is
394-
covariant, and `'static` is a subtype of *any* `'a`, so `&'static str` is a
354+
variant, and `'static` is a subtype of *any* `'a`, so `&'static str` is a
395355
subtype of `&'a str`. Therefore, if `&mut` was
396-
*also* covariant, then the lifetime of the `&'static str` would successfully be
356+
*also* variant, then the lifetime of the `&'static str` would successfully be
397357
"shrunk" down to the shorter lifetime of the string, and `replace` would be
398358
called successfully. The string would subsequently be dropped, and `forever_str`
399359
would point to freed memory when we print it!
400360

401-
Therefore `&mut` should be invariant. This is the general theme of covariance vs
402-
invariance: if covariance would allow you to *store* a short-lived value in a
361+
Therefore `&mut` should be invariant. This is the general theme of variance vs
362+
invariance: if variance would allow you to *store* a short-lived value in a
403363
longer-lived slot, then you must be invariant.
404364

405-
`Box` and `Vec` are interesting cases because they're covariant, but you can
365+
`Box` and `Vec` are interesting cases because they're variant, but you can
406366
definitely store values in them! This is fine because *you can only store values
407367
in them through a mutable reference*! The mutable reference makes the whole type
408368
invariant, and therefore prevents you from getting in trouble.
409369

410-
Being covariant allows them to be covariant when shared immutably (so you can pass
370+
Being variant allows them to be variant when shared immutably (so you can pass
411371
a `&Box<&'static str>` where a `&Box<&'a str>` is expected). It also allows you to
412372
forever weaken the type by moving it into a weaker slot. That is, you can do:
413373

414374
```rust
415375
fn get_box<'a>(&'a u8) -> Box<&'a str> {
376+
// string literals are `&'static str`s
416377
Box::new("hello")
417378
}
418379
```
@@ -424,51 +385,67 @@ The variance of the cell types similarly follows. `&` is like an `&mut` for a
424385
cell, because you can still store values in them through an `&`. Therefore cells
425386
must be invariant to avoid lifetime smuggling.
426387

427-
`Fn` is the most confusing case, largely because contravariance is easily the
428-
most confusing kind of variance, and basically never comes up. To understand it,
429-
consider a function `len` that takes a function `F`.
388+
`Fn` is the most subtle case, because it has mixed variance. To see why
389+
`Fn(T) -> U` should be invariant over T, consider the following function
390+
signature:
430391

431392
```rust
432-
fn len<F>(func: F) -> usize
433-
where F: Fn(&'static str) -> usize
434-
{
435-
func("hello")
436-
}
393+
// 'a is derived from some parent scope
394+
fn foo(&'a str) -> usize;
437395
```
438396

439-
We require that F is a Fn that can take an `&'static str` and returns a usize. Now
440-
say we have a function that can take an `&'a str` (for *some* `'a`). Such a function actually
441-
accepts *more* inputs, since `&'static str` is a subtype of `&'a str`. Therefore
442-
`len` should happily accept such a function!
397+
This signature claims that it can handle any &str that lives *at least* as long
398+
as `'a`. Now if this signature was variant with respect to &str, that would mean
399+
400+
```rust
401+
fn foo(&'static str) -> usize;
402+
```
443403

444-
So a `Fn(&'a str)` is a subtype of a `Fn(&'static str)` because
445-
`&'static str` is a subtype of `&'a str`. Exactly contravariance.
404+
could be provided in its place, as it would be a subtype. However this function
405+
has a *stronger* requirement: it says that it can *only* handle `&'static str`s,
406+
and nothing else. Therefore functions are not variant over their arguments.
446407

447-
The variance of `*const` and `*mut` is basically arbitrary as they're not at all
448-
type or memory safe, so their variance is determined in analogy to & and &mut
449-
respectively.
408+
To see why `Fn(T) -> U` should be *variant* over U, consider the following
409+
function signature:
410+
411+
```rust
412+
// 'a is derived from some parent scope
413+
fn foo(usize) -> &'a str;
414+
```
415+
416+
This signature claims that it will return something that outlives `'a`. It is
417+
therefore completely reasonable to provide
418+
419+
```rust
420+
fn foo(usize) -> &'static str;
421+
```
422+
423+
in its place. Therefore functions *are* variant over their return type.
424+
425+
`*const` has the exact same semantics as &, so variance follows. `*mut` on the
426+
other hand can dereference to an &mut whether shared or not, so it is marked
427+
as invariant in analogy to cells.
450428

451429
This is all well and good for the types the standard library provides, but
452-
how is variance determined for type that *you* define? A struct informally
453-
speaking inherits the variance of its fields. If a struct `Foo`
430+
how is variance determined for type that *you* define? A struct, informally
431+
speaking, inherits the variance of its fields. If a struct `Foo`
454432
has a generic argument `A` that is used in a field `a`, then Foo's variance
455433
over `A` is exactly `a`'s variance. However this is complicated if `A` is used
456434
in multiple fields.
457435

458-
* If all uses of A are covariant, then Foo is covariant over A
459-
* If all uses of A are contravariant, then Foo is contravariant over A
436+
* If all uses of A are variant, then Foo is variant over A
460437
* Otherwise, Foo is invariant over A
461438

462439
```rust
463440
struct Foo<'a, 'b, A, B, C, D, E, F, G, H> {
464-
a: &'a A, // covariant over 'a and A
441+
a: &'a A, // variant over 'a and A
465442
b: &'b mut B, // invariant over 'b and B
466-
c: *const C, // covariant over C
443+
c: *const C, // variant over C
467444
d: *mut D, // invariant over D
468-
e: Vec<E>, // covariant over E
445+
e: Vec<E>, // variant over E
469446
f: Cell<F>, // invariant over F
470-
g: G // covariant over G
471-
h1: H // would also be covariant over H except...
447+
g: G // variant over G
448+
h1: H // would also be variant over H except...
472449
h2: Cell<H> // invariant over H, because invariance wins
473450
}
474451
```
@@ -497,8 +474,9 @@ correct variance and drop checking.
497474

498475
We do this using *PhantomData*, which is a special marker type. PhantomData
499476
consumes no space, but simulates a field of the given type for the purpose of
500-
variance. This was deemed to be less error-prone than explicitly telling the
501-
type-system the kind of variance that you want.
477+
static analysis. This was deemed to be less error-prone than explicitly telling
478+
the type-system the kind of variance that you want, while also providing other
479+
useful information.
502480

503481
Iter logically contains `&'a T`, so this is exactly what we tell
504482
the PhantomData to simulate:
@@ -526,16 +504,16 @@ However the one exception is with PhantomData. Given a struct like Vec:
526504

527505
```
528506
struct Vec<T> {
529-
data: *const T, // *const for covariance!
507+
data: *const T, // *const for variance!
530508
len: usize,
531509
cap: usize,
532510
}
533511
```
534512

535-
dropck will generously determine that Vec<T> does not contain any values of
513+
dropck will generously determine that Vec<T> does not own any values of
536514
type T. This will unfortunately allow people to construct unsound Drop
537515
implementations that access data that has already been dropped. In order to
538-
tell dropck that we *do* own values of type T and may call destructors of that
516+
tell dropck that we *do* own values of type T, and may call destructors of that
539517
type, we must add extra PhantomData:
540518

541519
```
@@ -700,3 +678,56 @@ Bar is *also* live. Since IterMut is always live when `next` can be called, if
700678
to it exist!
701679

702680

681+
682+
683+
684+
# Weird Lifetimes
685+
686+
Given the following code:
687+
688+
```rust
689+
struct Foo;
690+
691+
impl Foo {
692+
fn mutate_and_share(&mut self) -> &Self { &*self }
693+
fn share(&self) {}
694+
}
695+
696+
fn main() {
697+
let mut foo = Foo;
698+
let loan = foo.mutate_and_share();
699+
foo.share();
700+
}
701+
```
702+
703+
One might expect it to compile. We call `mutate_and_share`, which mutably borrows
704+
`foo` *temporarily*, but then returns *only* a shared reference. Therefore we
705+
would expect `foo.share()` to succeed as `foo` shouldn't be mutably borrowed.
706+
707+
However when we try to compile it:
708+
709+
```text
710+
<anon>:11:5: 11:8 error: cannot borrow `foo` as immutable because it is also borrowed as mutable
711+
<anon>:11 foo.share();
712+
^~~
713+
<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
714+
<anon>:10 let loan = foo.mutate_and_share();
715+
^~~
716+
<anon>:12:2: 12:2 note: previous borrow ends here
717+
<anon>:8 fn main() {
718+
<anon>:9 let mut foo = Foo;
719+
<anon>:10 let loan = foo.mutate_and_share();
720+
<anon>:11 foo.share();
721+
<anon>:12 }
722+
^
723+
```
724+
725+
What happened? Well, the lifetime of `loan` is derived from a *mutable* borrow.
726+
This makes the type system believe that `foo` is mutably borrowed as long as
727+
`loan` exists, even though it's a shared reference. This isn't a bug, although
728+
one could argue it is a limitation of the design. In particular, to know if
729+
the mutable part of the borrow is *really* expired we'd have to peek into
730+
implementation details of the function. Currently, type-checking a function
731+
does not need to inspect the bodies of any other functions or types.
732+
733+

0 commit comments

Comments
 (0)