1
1
% Subtyping and Variance
2
2
3
- Although Rust doesn't have any notion of inheritance, it * does* include subtyping.
4
- In Rust, subtyping derives entirely from * lifetimes* . Since lifetimes are scopes,
5
- we can partially order them based on the * contains* (outlives) relationship. We
6
- can even express this as a generic bound.
3
+ Although Rust doesn't have any notion of inheritance, it * does* include
4
+ subtyping. In Rust, subtyping derives entirely from * lifetimes* . Since lifetimes
5
+ are scopes, we can partially order them based on the * contains* (outlives)
6
+ relationship. We can even express this as a generic bound.
7
7
8
- Subtyping on lifetimes in terms of that relationship: if ` 'a: 'b `
9
- ("a contains b" or "a outlives b"), then ` 'a ` is a subtype of ` 'b ` . This is a
10
- large source of confusion, because it seems intuitively backwards to many:
11
- the bigger scope is a * sub type* of the smaller scope.
8
+ Subtyping on lifetimes in terms of that relationship: if ` 'a: 'b ` ("a contains
9
+ b" or "a outlives b"), then ` 'a ` is a subtype of ` 'b ` . This is a large source of
10
+ confusion, because it seems intuitively backwards to many: the bigger scope is a
11
+ * sub type* of the smaller scope.
12
12
13
13
This does in fact make sense, though. The intuitive reason for this is that if
14
- you expect an ` &'a u8 ` , then it's totally fine for me to hand you an ` &'static u8 ` ,
15
- in the same way that if you expect an Animal in Java, it's totally fine for me to
16
- hand you a Cat. Cats are just Animals * and more* , just as ` 'static ` is just ` 'a `
17
- * and more* .
14
+ you expect an ` &'a u8 ` , then it's totally fine for me to hand you an `&'static
15
+ u8`, in the same way that if you expect an Animal in Java, it's totally fine for
16
+ me to hand you a Cat. Cats are just Animals * and more* , just as ` 'static ` is
17
+ just ` 'a ` * and more* .
18
18
19
- (Note, the subtyping relationship and typed-ness of lifetimes is a fairly arbitrary
20
- construct that some disagree with. However it simplifies our analysis to treat
21
- lifetimes and types uniformly.)
19
+ (Note, the subtyping relationship and typed-ness of lifetimes is a fairly
20
+ arbitrary construct that some disagree with. However it simplifies our analysis
21
+ to treat lifetimes and types uniformly.)
22
22
23
- Higher-ranked lifetimes are also subtypes of every concrete lifetime. This is because
24
- taking an arbitrary lifetime is strictly more general than taking a specific one.
23
+ Higher-ranked lifetimes are also subtypes of every concrete lifetime. This is
24
+ because taking an arbitrary lifetime is strictly more general than taking a
25
+ specific one.
25
26
26
27
27
28
28
29
# Variance
29
30
30
31
Variance is where things get a bit complicated.
31
32
32
- Variance is a property that * type constructors* have. A type constructor in Rust
33
- is a generic type with unbound arguments. For instance ` Vec ` is a type constructor
34
- that takes a ` T ` and returns a ` Vec<T> ` . ` & ` and ` &mut ` are type constructors that
35
- take a two types: a lifetime, and a type to point to.
33
+ Variance is a property that * type constructors* have with respect to their
34
+ arguments. A type constructor in Rust is a generic type with unbound arguments.
35
+ For instance ` Vec ` is a type constructor that takes a ` T ` and returns a
36
+ ` Vec<T> ` . ` & ` and ` &mut ` are type constructors that take a two types: a
37
+ lifetime, and a type to point to.
36
38
37
39
A type constructor's * variance* is how the subtyping of its inputs affects the
38
40
subtyping of its outputs. There are two kinds of variance in Rust:
39
41
40
- * F is * variant* if ` T ` being a subtype of ` U ` implies ` F<T> ` is a subtype of ` F<U> `
41
- * F is * invariant* otherwise (no subtyping relation can be derived)
42
+ * F is * variant* over ` T ` if ` T ` being a subtype of ` U ` implies
43
+ ` F<T> ` is a subtype of ` F<U> ` (subtyping "passes through")
44
+ * F is * invariant* over ` T ` otherwise (no subtyping relation can be derived)
42
45
43
- (For those of you who are familiar with variance from other languages, what we refer
44
- to as "just" variance is in fact * covariance* . Rust does not have contravariance.
45
- Historically Rust did have some contravariance but it was scrapped due to poor
46
- interactions with other features.)
46
+ (For those of you who are familiar with variance from other languages, what we
47
+ refer to as "just" variance is in fact * covariance* . Rust does not have
48
+ contravariance. Historically Rust did have some contravariance but it was
49
+ scrapped due to poor interactions with other features. If you experience
50
+ contravariance in Rust call your local compiler developer for medical advice.)
47
51
48
52
Some important variances:
49
53
50
- * ` & ` is variant (as is ` *const ` by metaphor)
51
- * ` &mut ` is invariant
52
- * ` Fn(T) -> U ` is invariant with respect to ` T ` , but variant with respect to ` U `
53
- * ` Box ` , ` Vec ` , and all other collections are variant
54
- * ` UnsafeCell ` , ` Cell ` , ` RefCell ` , ` Mutex ` and all "interior mutability"
55
- types are invariant (as is ` *mut ` by metaphor)
54
+ * ` &'a T ` is variant over ` 'a ` and ` T ` (as is ` *const T ` by metaphor)
55
+ * ` &'a mut T ` is variant with over ` 'a ` but invariant over ` T `
56
+ * ` Fn(T) -> U ` is invariant over ` T ` , but variant over ` U `
57
+ * ` Box ` , ` Vec ` , and all other collections are variant over their contents
58
+ * ` UnsafeCell<T> ` , ` Cell<T> ` , ` RefCell<T> ` , ` Mutex<T> ` and all other
59
+ interior mutability types are invariant over T (as is ` *mut T ` by metaphor)
56
60
57
- To understand why these variances are correct and desirable, we will consider several
58
- examples. We have already covered why ` & ` should be variant when introducing subtyping:
59
- it's desirable to be able to pass longer-lived things where shorter-lived things are
60
- needed.
61
+ To understand why these variances are correct and desirable, we will consider
62
+ several examples.
61
63
62
- To see why ` &mut ` should be invariant, consider the following code:
64
+
65
+ We have already covered why ` &'a T ` should be variant over ` 'a ` when
66
+ introducing subtyping: it's desirable to be able to pass longer-lived things
67
+ where shorter-lived things are needed.
68
+
69
+ Similar reasoning applies to why it should be variant over T. It is reasonable
70
+ to be able to pass ` &&'static str ` where an ` &&'a str ` is expected. The
71
+ additional level of indirection does not change the desire to be able to pass
72
+ longer lived things where shorted lived things are expected.
73
+
74
+ However this logic * does not* apply to see why ` &mut ` . To see why &mut should
75
+ be invariant over T, consider the following code:
63
76
64
77
``` rust,ignore
65
78
fn overwrite<T: Copy>(input: &mut T, new: &mut T) {
@@ -78,17 +91,24 @@ fn main() {
78
91
```
79
92
80
93
The signature of ` overwrite ` is clearly valid: it takes mutable references to
81
- two values of the same type, and overwrites one with the other. If ` &mut ` was
82
- variant, then ` &mut &'a str ` would be a subtype of ` &mut &'static str ` , since
83
- ` &'a str ` is a subtype of ` &'static str ` . Therefore the lifetime of
94
+ two values of the same type, and overwrites one with the other. If ` &mut T ` was
95
+ variant over T , then ` &mut &'a str ` would be a subtype of ` &mut &'static str ` ,
96
+ since ` &'a str ` is a subtype of ` &'static str ` . Therefore the lifetime of
84
97
` forever_str ` would successfully be "shrunk" down to the shorter lifetime of
85
98
` string ` , and ` overwrite ` would be called successfully. ` string ` would
86
99
subsequently be dropped, and ` forever_str ` would point to freed memory when we
87
100
print it! Therefore ` &mut ` should be invariant.
88
101
89
- This is the general theme of variance vs
90
- invariance: if variance would allow you to * store* a short-lived value in a
91
- longer-lived slot, then you must be invariant.
102
+ This is the general theme of variance vs invariance: if variance would allow you
103
+ to * store* a short-lived value over a longer-lived slot, then you must be
104
+ invariant.
105
+
106
+ However it * is* sound for ` &'a mut T ` to be variant over ` 'a ` . The key difference
107
+ between ` 'a ` and T is that ` 'a ` is a property of the reference itself,
108
+ while T is something the reference is borrowing. If you change T's type, then
109
+ the source still remembers the original type. However if you change the
110
+ lifetime's type, no one but the reference knows this information, so it's fine.
111
+ Put another way, ` &'a mut T ` owns ` 'a ` , but only * borrows* T.
92
112
93
113
` Box ` and ` Vec ` are interesting cases because they're variant, but you can
94
114
definitely store values in them! This is where Rust gets really clever: it's
@@ -115,9 +135,9 @@ Weakening when you pass by-value is fine because there's no one else who
115
135
trouble was because there's always someone else who remembers the original
116
136
subtype: the actual owner.
117
137
118
- The invariance of the cell types can be seen as follows: ` & ` is like an ` &mut ` for a
119
- cell, because you can still store values in them through an ` & ` . Therefore cells
120
- must be invariant to avoid lifetime smuggling.
138
+ The invariance of the cell types can be seen as follows: ` & ` is like an ` &mut `
139
+ for a cell, because you can still store values in them through an ` & ` . Therefore
140
+ cells must be invariant to avoid lifetime smuggling.
121
141
122
142
` Fn ` is the most subtle case because it has mixed variance. To see why
123
143
` Fn(T) -> U ` should be invariant over T, consider the following function
@@ -128,8 +148,9 @@ signature:
128
148
fn foo(&'a str) -> usize;
129
149
```
130
150
131
- This signature claims that it can handle any ` &str ` that lives * at least* as long
132
- as ` 'a ` . Now if this signature was variant with respect to ` &str ` , that would mean
151
+ This signature claims that it can handle any ` &str ` that lives * at least* as
152
+ long as ` 'a ` . Now if this signature was variant over ` &'a str ` , that
153
+ would mean
133
154
134
155
``` rust,ignore
135
156
fn foo(&'static str) -> usize;
0 commit comments