2
2
3
3
Although Rust doesn't have any notion of inheritance, it * does* include subtyping.
4
4
In Rust, subtyping derives entirely from * lifetimes* . Since lifetimes are scopes,
5
- we can partially order them based on a * contains* (outlives) relationship. We
6
- can even express this as a generic bound: ` T: 'a ` specifies that whatever scope ` T `
7
- is valid for must contain the scope ` 'a ` ("T outlives ` 'a ` ").
5
+ we can partially order them based on the * contains* (outlives) relationship. We
6
+ can even express this as a generic bound.
8
7
9
- We can then define subtyping on lifetimes in terms of that relationship: if ` 'a: 'b `
8
+ Subtyping on lifetimes in terms of that relationship: if ` 'a: 'b `
10
9
("a contains b" or "a outlives b"), then ` 'a ` is a subtype of ` 'b ` . This is a
11
10
large source of confusion, because it seems intuitively backwards to many:
12
11
the bigger scope is a * sub type* of the smaller scope.
13
12
14
- This does in fact make sense. The intuitive reason for this is that if you expect an
15
- ` &'a u8 ` , then it's totally fine for me to hand you an ` &'static u8 ` , in the same way
16
- that if you expect an Animal in Java, it's totally fine for me to hand you a Cat.
17
- Cats are just Animals * and more* , just as ` 'static ` is just ` 'a ` * and more* .
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* .
18
18
19
19
(Note, the subtyping relationship and typed-ness of lifetimes is a fairly arbitrary
20
- construct that some disagree with. I just find that it simplifies this analysis.)
20
+ construct that some disagree with. However it simplifies our analysis to treat
21
+ lifetimes and types uniformly.)
21
22
22
23
Higher-ranked lifetimes are also subtypes of every concrete lifetime. This is because
23
24
taking an arbitrary lifetime is strictly more general than taking a specific one.
@@ -26,15 +27,15 @@ taking an arbitrary lifetime is strictly more general than taking a specific one
26
27
27
28
# Variance
28
29
29
- Variance is where things get really harsh .
30
+ Variance is where things get a bit complicated .
30
31
31
32
Variance is a property that * type constructors* have. A type constructor in Rust
32
33
is a generic type with unbound arguments. For instance ` Vec ` is a type constructor
33
34
that takes a ` T ` and returns a ` Vec<T> ` . ` & ` and ` &mut ` are type constructors that
34
- take a lifetime and a type.
35
+ take a two types: a lifetime, and a type to point to .
35
36
36
- A type constructor's * variance* is how the subtypes of its inputs affects the
37
- subtypes of its outputs. There are three kinds of variance:
37
+ A type constructor's * variance* is how the subtyping of its inputs affects the
38
+ subtyping of its outputs. There are two kinds of variance in Rust :
38
39
39
40
* F is * variant* if ` T ` being a subtype of ` U ` implies ` F<T> ` is a subtype of ` F<U> `
40
41
* F is * invariant* otherwise (no subtyping relation can be derived)
@@ -60,42 +61,47 @@ needed.
60
61
61
62
To see why ` &mut ` should be invariant, consider the following code:
62
63
63
- ``` rust
64
+ ``` rust,ignore
65
+ fn overwrite<T: Copy>(input: &mut T, new: &mut T) {
66
+ *input = *new;
67
+ }
68
+
64
69
fn main() {
65
70
let mut forever_str: &'static str = "hello";
66
71
{
67
72
let string = String::from("world");
68
73
overwrite(&mut forever_str, &mut &*string);
69
74
}
75
+ // Oops, printing free'd memory
70
76
println!("{}", forever_str);
71
77
}
72
-
73
- fn overwrite <T : Copy >(input : & mut T , new : & mut T ) {
74
- * input = * new ;
75
- }
76
78
```
77
79
78
- The signature of ` overwrite ` is clearly valid: it takes mutable references to two values
79
- of the same type, and overwrites one with the other. We have seen already that ` & ` is
80
- variant, and ` 'static ` is a subtype of * any * ` 'a ` , so ` &'static str ` is a
81
- subtype of ` &'a str ` . Therefore, if ` &mut ` was
82
- * also * variant, then the lifetime of the ` &'static str ` would successfully be
83
- "shrunk" down to the shorter lifetime of the string, and ` overwrite ` would be
84
- called successfully. The string would subsequently be dropped, and ` forever_str `
85
- would point to freed memory when we print it!
80
+ 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
84
+ ` forever_str ` would successfully be "shrunk" down to the shorter lifetime of
85
+ ` string ` , and ` overwrite ` would be called successfully. ` string ` would
86
+ subsequently be dropped, and ` forever_str ` would point to freed memory when we
87
+ print it! Therefore ` &mut ` should be invariant.
86
88
87
- Therefore ` &mut ` should be invariant. This is the general theme of variance vs
89
+ This is the general theme of variance vs
88
90
invariance: if variance would allow you to * store* a short-lived value in a
89
91
longer-lived slot, then you must be invariant.
90
92
91
93
` Box ` and ` Vec ` are interesting cases because they're variant, but you can
92
- definitely store values in them! This is fine because * you can only store values
93
- in them through a mutable reference* ! The mutable reference makes the whole type
94
- invariant, and therefore prevents you from getting in trouble.
94
+ definitely store values in them! This is where Rust gets really clever: it's
95
+ fine for them to be variant because you can only store values
96
+ in them * via a mutable reference* ! The mutable reference makes the whole type
97
+ invariant, and therefore prevents you from smuggling a short-lived type into
98
+ them.
99
+
100
+ Being variant * does* allows them to be weakened when shared immutably.
101
+ So you can pass a ` &Box<&'static str> ` where a ` &Box<&'a str> ` is expected.
95
102
96
- Being variant allows them to be variant when shared immutably (so you can pass
97
- a ` &Box<&'static str> ` where a ` &Box<&'a str> ` is expected). It also allows you to
98
- forever weaken the type by moving it into a weaker slot. That is, you can do:
103
+ However what should happen when passing * by-value* is less obvious. It turns out
104
+ that, yes, you can use subtyping when passing by-value. That is, this works:
99
105
100
106
``` rust
101
107
fn get_box <'a >(& 'a u8 ) -> Box <& 'a str > {
@@ -104,14 +110,16 @@ fn get_box<'a>(&'a u8) -> Box<&'a str> {
104
110
}
105
111
```
106
112
107
- which is fine because unlike the mutable borrow case, there's no one else who
108
- "remembers" the old lifetime in the box.
113
+ Weakening when you pass by-value is fine because there's no one else who
114
+ "remembers" the old lifetime in the Box. The reason a variant ` &mut ` was
115
+ trouble was because there's always someone else who remembers the original
116
+ subtype: the actual owner.
109
117
110
- The variance of the cell types similarly follows. ` & ` is like an ` &mut ` for a
118
+ The invariance of the cell types can be seen as follows: ` & ` is like an ` &mut ` for a
111
119
cell, because you can still store values in them through an ` & ` . Therefore cells
112
120
must be invariant to avoid lifetime smuggling.
113
121
114
- ` Fn ` is the most subtle case, because it has mixed variance. To see why
122
+ ` Fn ` is the most subtle case because it has mixed variance. To see why
115
123
` Fn(T) -> U ` should be invariant over T, consider the following function
116
124
signature:
117
125
@@ -120,7 +128,7 @@ signature:
120
128
fn foo (& 'a str ) -> usize ;
121
129
```
122
130
123
- This signature claims that it can handle any &str that lives * at least* as long
131
+ This signature claims that it can handle any ` &str ` that lives * at least* as long
124
132
as ` 'a ` . Now if this signature was variant with respect to ` &str ` , that would mean
125
133
126
134
``` rust
0 commit comments