You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rust uses lifetimes to track the relationships between borrows and ownership.
4
4
However, a naive implementation of lifetimes would be either too restrictive,
5
-
or permit undefined behaviour. Let's see a few examples:
5
+
or permit undefined behavior.
6
6
7
-
```rust,ignore
8
-
fn debug<'a>(a: &'a str, b: &'a str) {
9
-
println!("a = {:?} b = {:?}", a, b)
7
+
In order to allow flexible usage of lifetimes
8
+
while also preventing mis-use, Rust uses a combination of **Subtyping** and **Variance**.
9
+
10
+
## Subtyping
11
+
12
+
Subtyping is the idea that one type can be a *subtype* of another.
13
+
Let's define that `A: B` is equivalent to saying '`A` is a subtype of `B`'.
14
+
What this is suggesting to us is that the set of *requirements* that `B` defines
15
+
are completely satisfied by `A`. `A` may then have more requirements.
16
+
17
+
An example of simple subtyping that exists in the language are [supertraits](https://doc.rust-lang.org/stable/book/ch19-03-advanced-traits.html?highlight=supertraits#using-supertraits-to-require-one-traits-functionality-within-another-trait)
18
+
19
+
```rust
20
+
usestd::fmt;
21
+
22
+
traitOutlinePrint:fmt::Display {
23
+
fnoutline_print(&self) {
24
+
todo!()
25
+
}
26
+
}
27
+
```
28
+
29
+
Here, we have that `OutlinePrint: fmt::Display` (`OutlinePrint` is a *subtype* of `Display`),
30
+
because it has all the requirements of `fmt::Display`, plus the `outline_print` function.
31
+
32
+
However, subtyping in traits is not that interesting in the case of Rust.
33
+
Here in the nomicon, we're going to focus more with how subtyping interacts with **lifetimes**
34
+
35
+
Take this example
36
+
37
+
```rust
38
+
fndebug<T:std::fmt::Debug>(a:T, b:T) {
39
+
println!("a = {:?} b = {:?}", a, b);
10
40
}
11
41
12
42
fnmain() {
@@ -24,163 +54,73 @@ we might see the following error:
24
54
25
55
```text
26
56
error[E0308]: mismatched types
27
-
--> src/main.rs:6:16
28
-
|
29
-
6 | debug(a, b);
30
-
| ^
31
-
| |
32
-
| expected `&'static str`, found struct `&'b str`
57
+
--> src/main.rs:10:16
58
+
|
59
+
10 | debug(a, b);
60
+
| ^
61
+
| |
62
+
| expected `&'static str`, found struct `&'b str`
33
63
```
34
64
35
-
This is over-restrictive. In this case, what we want is to accept any type that lives "at least as long" as `<'a>`.
36
-
This is what subtyping is intended to fix.
37
-
38
-
Let's define lifetime `'a` to be a `subtype` of lifetime `'b`, if and only if `'a` lives _at least as long_ as `'b`.
39
-
We will denote this as `'a: 'b`
40
-
41
-
---
65
+
This is over-restrictive. In this case, what we want is to accept any type that lives *at least as long* as `'b`.
66
+
Let's try using subtyping with our lifetimes.
42
67
43
-
Subtyping is a relationship between types that allows statically typed
44
-
languages to be a bit more flexible and permissive.
68
+
Let's define a lifetime to have the a simple set of requirements: `'a` defines a region of code in which a value will be alive.
69
+
Now that we have a defined set of requirements for lifetimes, we can define how they relate to each other.
70
+
`'a: 'b` if and only if `'a` defines a region of code that **completely contains**`'b`.
45
71
46
-
Subtyping in Rust is a bit different from subtyping in other languages. This
47
-
makes it harder to give simple examples, which is a problem since subtyping,
48
-
and especially variance, is already hard to understand properly. As in,
49
-
even compiler writers mess it up all the time.
72
+
`'a` may define a region larger than `'b`, but that still fits our definition.
73
+
Going back to our example above, we can say that `'static: 'b`.
50
74
51
-
To keep things simple, this section will consider a small extension to the
52
-
Rust language that adds a new and simpler subtyping relationship. After
53
-
establishing concepts and issues under this simpler system,
54
-
we will then relate it back to how subtyping actually occurs in Rust.
55
-
56
-
So here's our simple extension, *Objective Rust*, featuring three new types:
75
+
For now, let's accept the idea that subtypes of lifetimes can be transitive (more on this in [Variance](#variance>)),
76
+
eg. `&'static str` is a subtype of `&'b str`, then we can let them coerce, and then the example above will compile
57
77
58
78
```rust
59
-
traitAnimal {
60
-
fnsnuggle(&self);
61
-
fneat(&mutself);
62
-
}
63
-
64
-
traitCat:Animal {
65
-
fnmeow(&self);
79
+
fndebug<T:std::fmt::Debug>(a:T, b:T) {
80
+
println!("a = {:?} b = {:?}", a, b);
66
81
}
67
82
68
-
traitDog:Animal {
69
-
fnbark(&self);
70
-
}
71
-
```
72
-
73
-
But unlike normal traits, we can use them as concrete and sized types, just like structs.
74
-
75
-
Now, say we have a very simple function that takes an Animal, like this:
76
-
77
-
<!-- ignore: simplified code -->
78
-
```rust,ignore
79
-
fn love(pet: Animal) {
80
-
pet.snuggle();
83
+
fnmain() {
84
+
leta:&'staticstr="hello";
85
+
{
86
+
letb=String::from("world");
87
+
letb=&b; // 'b has a shorter lifetime than 'static
88
+
debug(a, b); // a silently converts from `&'static str` into `&'b str`
89
+
}
81
90
}
82
91
```
83
92
84
-
By default, static types must match *exactly* for a program to compile. As such,
85
-
this code won't compile:
86
-
87
-
<!-- ignore: simplified code -->
88
-
```rust,ignore
89
-
let mr_snuggles: Cat = ...;
90
-
love(mr_snuggles); // ERROR: expected Animal, found Cat
91
-
```
92
-
93
-
Mr. Snuggles is a Cat, and Cats aren't *exactly* Animals, so we can't love him! 😿
94
-
95
-
This is annoying because Cats *are* Animals. They support every operation
96
-
an Animal supports, so intuitively `love` shouldn't care if we pass it a `Cat`.
97
-
We should be able to just **forget** the non-animal parts of our `Cat`, as they
98
-
aren't necessary to love it.
99
-
100
-
This is exactly the problem that *subtyping* is intended to fix. Because Cats are just
101
-
Animals **and more**, we say Cat is a *subtype* of Animal (because Cats are a *subset*
102
-
of all the Animals). Equivalently, we say that Animal is a *supertype* of Cat.
103
-
With subtypes, we can tweak our overly strict static type system
104
-
with a simple rule: anywhere a value of type `T` is expected, we will also
105
-
accept values that are subtypes of `T`.
106
-
107
-
Or more concretely: anywhere an Animal is expected, a Cat or Dog will also work.
108
-
109
-
As we will see throughout the rest of this section, subtyping is a lot more complicated
110
-
and subtle than this, but this simple rule is a very good 99% intuition. And unless you
111
-
write unsafe code, the compiler will automatically handle all the corner cases for you.
112
-
113
-
But this is the Rustonomicon. We're writing unsafe code, so we need to understand how
114
-
this stuff really works, and how we can mess it up.
115
-
116
-
The core problem is that this rule, naively applied, will lead to *meowing Dogs*. That is,
117
-
we can convince someone that a Dog is actually a Cat. This completely destroys the fabric
118
-
of our static type system, making it worse than useless (and leading to Undefined Behavior).
119
-
120
-
Here's a simple example of this happening when we apply subtyping in a completely naive
121
-
"find and replace" way.
93
+
## Variance
122
94
123
-
<!-- ignore: simplified code -->
124
-
```rust,ignore
125
-
fn evil_feeder(pet: &mut Animal) {
126
-
let spike: Dog = ...;
95
+
Above, we glossed over the fact that `'static: 'b` implied that `&'static T: &'b T`. This uses a property known as variance.
96
+
It's not always as simple as this example though, to understand that let's try extend this example a bit
127
97
128
-
// `pet` is an Animal, and Dog is a subtype of Animal,
129
-
// so this should be fine, right..?
130
-
*pet = spike;
98
+
```rust,compile_fail
99
+
fn debug<T>(a: &mut T, b: T) {
100
+
*a = b;
131
101
}
132
102
133
103
fn main() {
134
-
let mut mr_snuggles: Cat = ...;
135
-
evil_feeder(&mut mr_snuggles); // Replaces mr_snuggles with a Dog
136
-
mr_snuggles.meow(); // OH NO, MEOWING DOG!
104
+
let mut a: &'static str = "hello";
105
+
{
106
+
let b = String::from("world");
107
+
let b = &b;
108
+
debug(&mut a, b);
109
+
}
137
110
}
138
111
```
139
112
140
-
Clearly, we need a more robust system than "find and replace". That system is *variance*,
141
-
which is a set of rules governing how subtyping should compose. Most importantly, variance
142
-
defines situations where subtyping should be disabled.
113
+
This has a memory bug in it.
143
114
144
-
But before we get into variance, let's take a quick peek at where subtyping actually occurs in
145
-
Rust: *lifetimes*!
146
-
147
-
> NOTE: The typed-ness of lifetimes is a fairly arbitrary construct that some
148
-
> disagree with. However it simplifies our analysis to treat lifetimes
149
-
> and types uniformly.
150
-
151
-
Lifetimes are just regions of code, and regions can be partially ordered with the *contains*
152
-
(outlives) relationship. Subtyping on lifetimes is in terms of that relationship:
153
-
if `'big: 'small` ("big contains small" or "big outlives small"), then `'big` is a subtype
154
-
of `'small`. This is a large source of confusion, because it seems backwards
155
-
to many: the bigger region is a *subtype* of the smaller region. But it makes
156
-
sense if you consider our Animal example: Cat is an Animal *and more*,
157
-
just as `'big` is `'small`*and more*.
158
-
159
-
Put another way, if someone wants a reference that lives for `'small`,
160
-
usually what they actually mean is that they want a reference that lives
161
-
for *at least*`'small`. They don't actually care if the lifetimes match
162
-
exactly. So it should be ok for us to **forget** that something lives for
163
-
`'big` and only remember that it lives for `'small`.
164
-
165
-
The meowing dog problem for lifetimes will result in us being able to
166
-
store a short-lived reference in a place that expects a longer-lived one,
167
-
creating a dangling reference and letting us use-after-free.
168
-
169
-
It will be useful to note that `'static`, the forever lifetime, is a subtype of
170
-
every lifetime because by definition it outlives everything. We will be using
171
-
this relationship in later examples to keep them as simple as possible.
172
-
173
-
With all that said, we still have no idea how to actually *use* subtyping of lifetimes,
174
-
because nothing ever has type `'a`. Lifetimes only occur as part of some larger type
175
-
like `&'a u32` or `IterMut<'a, u32>`. To apply lifetime subtyping, we need to know
176
-
how to compose subtyping. Once again, we need *variance*.
177
-
178
-
## Variance
115
+
If we were to expand this out, we'd see that we're trying to assign a `&'b str` into a `&'static str`,
116
+
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.
179
117
180
-
Variance is where things get a bit complicated.
118
+
However, the implementation of `debug` is valid.
119
+
Therefore, this must mean that `&mut &'static str` should **not** a *subtype* of `&mut &'b str`,
120
+
even if `'static` is a subtype of `'b`.
181
121
182
-
Variance is a property that *type constructors* have with respect to their
183
-
arguments. A type constructor in Rust is any generic type with unbound arguments.
122
+
Variance is the way that Rust defines the transitivity of subtypes through their*type constructor*.
123
+
A type constructor in Rust is any generic type with unbound arguments.
184
124
For instance `Vec` is a type constructor that takes a type `T` and returns
185
125
`Vec<T>`. `&` and `&mut` are type constructors that take two inputs: a
186
126
lifetime, and a type to point to.
@@ -192,20 +132,18 @@ A type constructor F's *variance* is how the subtyping of its inputs affects the
192
132
subtyping of its outputs. There are three kinds of variance in Rust. Given two
193
133
types `Sub` and `Super`, where `Sub` is a subtype of `Super`:
194
134
195
-
*`F` is *covariant* if `F<Sub>` is a subtype of `F<Super>` (subtyping "passes through")
196
-
*`F` is *contravariant* if `F<Super>` is a subtype of `F<Sub>` (subtyping is "inverted")
197
-
*`F` is *invariant* otherwise (no subtyping relationship exists)
135
+
*F is **covariant** if `F<Sub>` is a subtype of `F<Super>` (the subtype property is passed through)
136
+
*F is **contravariant** if `F<Super>` is a subtype of `F<Sub>` (the subtype property is "inverted")
137
+
*F is **invariant** otherwise (no subtyping relationship exists)
198
138
199
-
If `F` has multiple type parameters, we can talk about the individual variances
200
-
by saying that, for example, `F<T, U>` is covariant over `T` and invariant over `U`.
139
+
If we remember from the above examples,
140
+
it was ok for us to treat `&'a T` as a subtype of `&'b T` if `'a: 'b`,
141
+
therefore we can say that `&'a T` is *covariant* over `'a`.
201
142
202
-
It is very useful to keep in mind that covariance is, in practical terms, "the"
203
-
variance. Almost all consideration of variance is in terms of whether something
204
-
should be covariant or invariant. Actually witnessing contravariance is quite difficult
205
-
in Rust, though it does in fact exist.
143
+
Also, we saw that it was not ok for us to treat `&mut &'a T` as a subtype of `&mut &'b T`,
144
+
therefore we can say that `&mut T` is *invariant* over `T`
206
145
207
-
Here is a table of important variances which the rest of this section will be devoted
208
-
to trying to explain:
146
+
Here is a table of some other type constructors and their variances:
0 commit comments