Skip to content

Commit a432377

Browse files
conradludgateehuss
authored andcommitted
more explanations
1 parent 0492daf commit a432377

File tree

1 file changed

+92
-79
lines changed

1 file changed

+92
-79
lines changed

src/subtyping.md

Lines changed: 92 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ fn debug<T: std::fmt::Debug>(a: T, b: T) {
4242
}
4343

4444
fn main() {
45-
let a: &'static str = "hello";
45+
let hello: &'static str = "hello";
4646
{
47-
let b = String::from("world");
48-
let b = &b; // 'b has a shorter lifetime than 'static
49-
debug(a, b);
47+
let world = String::from("world");
48+
let world = &world; // 'b has a shorter lifetime than 'static
49+
debug(hello, world);
5050
}
5151
}
5252
```
@@ -58,10 +58,10 @@ we might see the following error:
5858
error[E0308]: mismatched types
5959
--> src/main.rs:10:16
6060
|
61-
10 | debug(a, b);
62-
| ^
63-
| |
64-
| expected `&'static str`, found struct `&'b str`
61+
10 | debug(hello, world);
62+
| ^
63+
| |
64+
| expected `&'static str`, found struct `&'b str`
6565
```
6666

6767
This is over-restrictive. In this case, what we want is to accept any type that lives *at least as long* as `'b`.
@@ -83,11 +83,11 @@ fn debug<T: std::fmt::Debug>(a: T, b: T) {
8383
}
8484

8585
fn main() {
86-
let a: &'static str = "hello";
86+
let hello: &'static str = "hello";
8787
{
88-
let b = String::from("world");
89-
let b = &b; // 'b has a shorter lifetime than 'static
90-
debug(a, b); // a silently converts from `&'static str` into `&'b str`
88+
let world = String::from("world");
89+
let world = &world; // 'b has a shorter lifetime than 'static
90+
debug(hello, world); // a silently converts from `&'static str` into `&'b str`
9191
}
9292
}
9393
```
@@ -98,26 +98,25 @@ Above, we glossed over the fact that `'static: 'b` implied that `&'static T: &'b
9898
It's not always as simple as this example though, to understand that let's try extend this example a bit
9999

100100
```rust,compile_fail
101-
fn debug<T>(a: &mut T, b: T) {
102-
*a = b;
101+
fn assign<T>(input: &mut T, val: T) {
102+
*input = val;
103103
}
104104
105105
fn main() {
106-
let mut a: &'static str = "hello";
106+
let mut hello: &'static str = "hello";
107107
{
108-
let b = String::from("world");
109-
let b = &b;
110-
debug(&mut a, b);
108+
let world = String::from("world");
109+
assign(&mut hello, &world);
111110
}
112111
}
113112
```
114113

115-
This has a memory bug in it.
114+
If this were to compile, this would have a memory bug.
116115

117116
If we were to expand this out, we'd see that we're trying to assign a `&'b str` into a `&'static str`,
118117
but the problem is that as soon as `b` goes out of scope, `a` is now invalid, even though it's supposed to have a `'static` lifetime.
119118

120-
However, the implementation of `debug` is valid.
119+
However, the implementation of `assign` is valid.
121120
Therefore, this must mean that `&mut &'static str` should **not** a *subtype* of `&mut &'b str`,
122121
even if `'static` is a subtype of `'b`.
123122

@@ -149,21 +148,21 @@ Here is a table of some other type constructors and their variances:
149148

150149
| | | 'a | T | U |
151150
|---|-----------------|:---------:|:-----------------:|:---------:|
152-
| * | `&'a T ` | covariant | covariant | |
153-
| * | `&'a mut T` | covariant | invariant | |
154-
| * | `Box<T>` | | covariant | |
151+
| | `&'a T ` | covariant | covariant | |
152+
| | `&'a mut T` | covariant | invariant | |
153+
| | `Box<T>` | | covariant | |
155154
| | `Vec<T>` | | covariant | |
156-
| * | `UnsafeCell<T>` | | invariant | |
155+
| | `UnsafeCell<T>` | | invariant | |
157156
| | `Cell<T>` | | invariant | |
158-
| * | `fn(T) -> U` | | **contra**variant | covariant |
157+
| | `fn(T) -> U` | | **contra**variant | covariant |
159158
| | `*const T` | | covariant | |
160159
| | `*mut T` | | invariant | |
161160

162-
The types with \*'s are the ones we will be focusing on, as they are in
163-
some sense "fundamental". All the others can be understood by analogy to the others:
161+
Some of these can be explained simply in relation to the others:
164162

165163
* `Vec<T>` and all other owning pointers and collections follow the same logic as `Box<T>`
166164
* `Cell<T>` and all other interior mutability types follow the same logic as `UnsafeCell<T>`
165+
* `UnsafeCell<T>` having interior mutability gives it the same variance properties as `&mut T`
167166
* `*const T` follows the logic of `&T`
168167
* `*mut T` follows the logic of `&mut T` (or `UnsafeCell<T>`)
169168

@@ -177,8 +176,72 @@ For more types, see the ["Variance" section][variance-table] on the reference.
177176
> take references with specific lifetimes (as opposed to the usual "any lifetime",
178177
> which gets into higher rank lifetimes, which work independently of subtyping).
179178
180-
Ok, that's enough type theory! Let's try to apply the concept of variance to Rust
181-
and look at some examples.
179+
Now that we have some more formal understanding of variance,
180+
let's go through some more examples in more detail.
181+
182+
```rust,compile_fail
183+
fn assign<T>(input: &mut T, val: T) {
184+
*input = val;
185+
}
186+
187+
fn main() {
188+
let mut hello: &'static str = "hello";
189+
{
190+
let world = String::from("world");
191+
assign(&mut hello, &world);
192+
}
193+
}
194+
```
195+
196+
And what do we get when we run this?
197+
198+
```text
199+
error[E0597]: `world` does not live long enough
200+
--> src/main.rs:9:28
201+
|
202+
6 | let mut hello: &'static str = "hello";
203+
| ------------ type annotation requires that `world` is borrowed for `'static`
204+
...
205+
9 | assign(&mut hello, &world);
206+
| ^^^^^^ borrowed value does not live long enough
207+
10 | }
208+
| - `world` dropped here while still borrowed
209+
```
210+
211+
Good, it doesn't compile! Let's break down what's happening here in detail.
212+
213+
First let's look at the `assign` function:
214+
215+
```rust
216+
fn assign<T>(input: &mut T, val: T) {
217+
*input = val;
218+
}
219+
```
220+
221+
All it does is take a mutable reference and a value and overwrite the referent with it.
222+
What's important about this function is that it creates a type equality constraint. It
223+
clearly says in its signature the referent and the value must be the *exact same* type.
224+
225+
Meanwhile, in the caller we pass in `&mut &'static str` and `&'spike_str str`.
226+
227+
Because `&mut T` is invariant over `T`, the compiler concludes it can't apply any subtyping
228+
to the first argument, and so `T` must be exactly `&'static str`.
229+
230+
This is counter to the `&T` case
231+
232+
```rust
233+
fn debug<T: std::fmt::Debug>(a: T, b: T) {
234+
println!("a = {:?} b = {:?}", a, b);
235+
}
236+
```
237+
238+
Where similarly `a` and `b` must have the same type `T`.
239+
But since `&'a T` *is* covariant over `'a`, we are allowed to perform subtyping.
240+
So the compiler decides that `&'static str` can become `&'b str` if and only if
241+
`&'static str` is a subtype of `&'b str`, which will hold if `'static: 'b`.
242+
This is true, so the compiler is happy to continue compiling this code.
243+
244+
---
182245

183246
First off, let's revisit the meowing dog example:
184247

@@ -249,56 +312,6 @@ enough into the place expecting something long-lived.
249312

250313
Here it is:
251314

252-
```rust,compile_fail
253-
fn evil_feeder<T>(input: &mut T, val: T) {
254-
*input = val;
255-
}
256-
257-
fn main() {
258-
let mut mr_snuggles: &'static str = "meow! :3"; // mr. snuggles forever!!
259-
{
260-
let spike = String::from("bark! >:V");
261-
let spike_str: &str = &spike; // Only lives for the block
262-
evil_feeder(&mut mr_snuggles, spike_str); // EVIL!
263-
}
264-
println!("{}", mr_snuggles); // Use after free?
265-
}
266-
```
267-
268-
And what do we get when we run this?
269-
270-
```text
271-
error[E0597]: `spike` does not live long enough
272-
--> src/main.rs:9:31
273-
|
274-
6 | let mut mr_snuggles: &'static str = "meow! :3"; // mr. snuggles forever!!
275-
| ------------ type annotation requires that `spike` is borrowed for `'static`
276-
...
277-
9 | let spike_str: &str = &spike; // Only lives for the block
278-
| ^^^^^^ borrowed value does not live long enough
279-
10 | evil_feeder(&mut mr_snuggles, spike_str); // EVIL!
280-
11 | }
281-
| - `spike` dropped here while still borrowed
282-
```
283-
284-
Good, it doesn't compile! Let's break down what's happening here in detail.
285-
286-
First let's look at the new `evil_feeder` function:
287-
288-
```rust
289-
fn evil_feeder<T>(input: &mut T, val: T) {
290-
*input = val;
291-
}
292-
```
293-
294-
All it does is take a mutable reference and a value and overwrite the referent with it.
295-
What's important about this function is that it creates a type equality constraint. It
296-
clearly says in its signature the referent and the value must be the *exact same* type.
297-
298-
Meanwhile, in the caller we pass in `&mut &'static str` and `&'spike_str str`.
299-
300-
Because `&mut T` is invariant over `T`, the compiler concludes it can't apply any subtyping
301-
to the first argument, and so `T` must be exactly `&'static str`.
302315

303316
The other argument is only an `&'a str`, which *is* covariant over `'a`. So the compiler
304317
adopts a constraint: `&'spike_str str` must be a subtype of `&'static str` (inclusive),

0 commit comments

Comments
 (0)