Skip to content

Commit 1517460

Browse files
conradludgateehuss
authored andcommitted
remove supertraits
replace subtyping syntax remove type constructors
1 parent 86b1c87 commit 1517460

File tree

1 file changed

+27
-50
lines changed

1 file changed

+27
-50
lines changed

src/subtyping.md

+27-50
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ while also preventing their misuse, Rust uses **subtyping** and **variance**.
1010
Let's start with an example.
1111

1212
```rust
13+
// Note: debug expects two parameters with the *same* lifetime
1314
fn debug<'a>(a: &'a str, b: &'a str) {
1415
println!("a = {:?} b = {:?}", a, b);
1516
}
@@ -45,38 +46,18 @@ Let's try using subtyping with our lifetimes.
4546

4647
Subtyping is the idea that one type can be used in place of another.
4748

48-
Let's define that `Sub` is a subtype of `Super` (we'll be using the notation `Sub: Super` throughout this chapter).
49+
Let's define that `Sub` is a subtype of `Super` (we'll be using the notation `Sub <: Super` throughout this chapter).
4950

5051
What this is suggesting to us is that the set of *requirements* that `Super` defines
5152
are completely satisfied by `Sub`. `Sub` may then have more requirements.
5253

53-
An example of simple subtyping that exists in the language is [supertraits][supertraits]:
54-
55-
[supertraits]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-supertraits-to-require-one-traits-functionality-within-another-trait
56-
57-
```rust
58-
use std::fmt;
59-
60-
pub trait Error: fmt::Display {
61-
fn source(&self) -> Option<&(dyn Error + 'static)>;
62-
fn description(&self) -> &str;
63-
fn cause(&self) -> Option<&dyn Error>;
64-
}
65-
```
66-
67-
Here, we have that `Error: fmt::Display` (`Error` is a *subtype* of `Display`),
68-
because it has all the requirements of `fmt::Display`, plus the `source`/`description`/`cause` functions.
69-
70-
However, subtyping in traits is not that interesting.
71-
Here in the Rustonomicon, we're going to focus more with how subtyping interacts with lifetimes.
72-
73-
Let's define a lifetime to be the simple requirement:
54+
Now, in order to use subtyping with lifetimes, we need to define the requirement of a lifetime:
7455

7556
> `'a` defines a region of code.
7657
7758
Now that we have a defined set of requirements for lifetimes, we can define how they relate to each other:
7859

79-
> `'long : 'short` if and only if `'long` defines a region of code that **completely contains** `'short`.
60+
> `'long <: 'short` if and only if `'long` defines a region of code that **completely contains** `'short`.
8061
8162
`'long` may define a region larger than `'short`, but that still fits our definition.
8263

@@ -88,7 +69,7 @@ And unless you write unsafe code, the compiler will automatically handle all the
8869
> But this is the Rustonomicon. We're writing unsafe code,
8970
so we need to understand how this stuff really works, and how we can mess it up.
9071

91-
Going back to our example above, we can say that `'static : 'world`.
72+
Going back to our example above, we can say that `'static <: 'world`.
9273
For now, let's also accept the idea that subtypes of lifetimes can be passed through references
9374
(more on this in [Variance](#variance)),
9475
_e.g._ `&'static str` is a subtype of `&'world str`, then we can let a `&'static str` "downgrade" into a `&'world str`.
@@ -111,7 +92,7 @@ fn main() {
11192

11293
## Variance
11394

114-
Above, we glossed over the fact that `'static : 'b` implied that `&'static T : &'b T`. This uses a property known as _variance_.
95+
Above, we glossed over the fact that `'static <: 'b` implied that `&'static T <: &'b T`. This uses a property known as _variance_.
11596
It's not always as simple as this example, though. To understand that, let's try to extend this example a bit:
11697

11798
```rust,compile_fail
@@ -141,43 +122,39 @@ The problem is that we cannot assume that `&mut &'static str` and `&mut &'b str`
141122
This means that `&mut &'static str` **cannot** be a *subtype* of `&mut &'b str`,
142123
even if `'static` is a subtype of `'b`.
143124

144-
Variance is the concept that Rust borrows to define relationships about subtypes through their *type constructor*s.
145-
A type constructor is any generic item in Rust.
146-
For instance `Vec` is a type constructor that takes a type `T` and returns
147-
`Vec<T>`. `&` and `&mut` are type constructors that take two inputs: a
148-
lifetime, and a type to point to.
125+
Variance is the concept that Rust borrows to define relationships about subtypes through their generic parameters.
149126

150-
> NOTE: For convenience we will often refer to `F<T>` as a type constructor just so
127+
> NOTE: For convenience we will define a generic type `F<T>` so
151128
> that we can easily talk about `T`. Hopefully this is clear in context.
152129
153-
A type constructor F's *variance* is how the subtyping of its inputs affects the
130+
The type `F`'s *variance* is how the subtyping of its inputs affects the
154131
subtyping of its outputs. There are three kinds of variance in Rust. Given two
155132
types `Sub` and `Super`, where `Sub` is a subtype of `Super`:
156133

157-
* F is **covariant** if `F<Sub>` is a subtype of `F<Super>` (the subtype property is passed through)
158-
* F is **contravariant** if `F<Super>` is a subtype of `F<Sub>` (the subtype property is "inverted")
159-
* F is **invariant** otherwise (no subtyping relationship exists)
134+
* `F` is **covariant** if `F<Sub>` is a subtype of `F<Super>` (the subtype property is passed through)
135+
* `F` is **contravariant** if `F<Super>` is a subtype of `F<Sub>` (the subtype property is "inverted")
136+
* `F` is **invariant** otherwise (no subtyping relationship exists)
160137

161138
If we remember from the above examples,
162-
it was ok for us to treat `&'a T` as a subtype of `&'b T` if `'a: 'b`,
139+
it was ok for us to treat `&'a T` as a subtype of `&'b T` if `'a <: 'b`,
163140
therefore we can say that `&'a T` is *covariant* over `'a`.
164141

165-
Also, we saw that it was not ok for us to treat `&mut &'a T` as a subtype of `&mut &'b T`,
142+
Also, we saw that it was not ok for us to treat `&mut &'a U` as a subtype of `&mut &'b U`,
166143
therefore we can say that `&mut T` is *invariant* over `T`
167144

168-
Here is a table of some other type constructors and their variances:
145+
Here is a table of some other generic types and their variances:
169146

170-
| | | 'a | T | U |
171-
|---|-----------------|:---------:|:-----------------:|:---------:|
172-
| | `&'a T ` | covariant | covariant | |
173-
| | `&'a mut T` | covariant | invariant | |
174-
| | `Box<T>` | | covariant | |
175-
| | `Vec<T>` | | covariant | |
176-
| | `UnsafeCell<T>` | | invariant | |
177-
| | `Cell<T>` | | invariant | |
178-
| | `fn(T) -> U` | | **contra**variant | covariant |
179-
| | `*const T` | | covariant | |
180-
| | `*mut T` | | invariant | |
147+
| | 'a | T | U |
148+
|-----------------|:---------:|:-----------------:|:---------:|
149+
| `&'a T ` | covariant | covariant | |
150+
| `&'a mut T` | covariant | invariant | |
151+
| `Box<T>` | | covariant | |
152+
| `Vec<T>` | | covariant | |
153+
| `UnsafeCell<T>` | | invariant | |
154+
| `Cell<T>` | | invariant | |
155+
| `fn(T) -> U` | | **contra**variant | covariant |
156+
| `*const T` | | covariant | |
157+
| `*mut T` | | invariant | |
181158

182159
Some of these can be explained simply in relation to the others:
183160

@@ -260,7 +237,7 @@ fn debug<T: std::fmt::Debug>(a: T, b: T) {
260237
where similarly `a` and `b` must have the same type `T`.
261238
But since `&'a T` *is* covariant over `'a`, we are allowed to perform subtyping.
262239
So the compiler decides that `&'static str` can become `&'b str` if and only if
263-
`&'static str` is a subtype of `&'b str`, which will hold if `'static: 'b`.
240+
`&'static str` is a subtype of `&'b str`, which will hold if `'static <: 'b`.
264241
This is true, so the compiler is happy to continue compiling this code.
265242

266243
As it turns out, the argument for why it's ok for Box (and Vec, HashMap, etc.) to be covariant is pretty similar to the argument for why it's ok for lifetimes to be covariant: as soon as you try to stuff them in something like a mutable reference, they inherit invariance and you're prevented from doing anything bad.

0 commit comments

Comments
 (0)