Skip to content

Commit 8e129cc

Browse files
conradludgateehuss
authored andcommitted
remove the rest of animals
1 parent a432377 commit 8e129cc

File tree

1 file changed

+15
-136
lines changed

1 file changed

+15
-136
lines changed

src/subtyping.md

Lines changed: 15 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -241,171 +241,50 @@ So the compiler decides that `&'static str` can become `&'b str` if and only if
241241
`&'static str` is a subtype of `&'b str`, which will hold if `'static: 'b`.
242242
This is true, so the compiler is happy to continue compiling this code.
243243

244-
---
245-
246-
First off, let's revisit the meowing dog example:
247-
248-
<!-- ignore: simplified code -->
249-
```rust,ignore
250-
fn evil_feeder(pet: &mut Animal) {
251-
let spike: Dog = ...;
252-
253-
// `pet` is an Animal, and Dog is a subtype of Animal,
254-
// so this should be fine, right..?
255-
*pet = spike;
256-
}
257-
258-
fn main() {
259-
let mut mr_snuggles: Cat = ...;
260-
evil_feeder(&mut mr_snuggles); // Replaces mr_snuggles with a Dog
261-
mr_snuggles.meow(); // OH NO, MEOWING DOG!
262-
}
263-
```
264-
265-
If we look at our table of variances, we see that `&mut T` is *invariant* over `T`.
266-
As it turns out, this completely fixes the issue! With invariance, the fact that
267-
Cat is a subtype of Animal doesn't matter; `&mut Cat` still won't be a subtype of
268-
`&mut Animal`. The static type checker will then correctly stop us from passing
269-
a Cat into `evil_feeder`.
270-
271-
The soundness of subtyping is based on the idea that it's ok to forget unnecessary
272-
details. But with references, there's always someone that remembers those details:
273-
the value being referenced. That value expects those details to keep being true,
274-
and may behave incorrectly if its expectations are violated.
275-
276-
The problem with making `&mut T` covariant over `T` is that it gives us the power
277-
to modify the original value *when we don't remember all of its constraints*.
278-
And so, we can make someone have a Dog when they're certain they still have a Cat.
279-
280-
With that established, we can easily see why `&T` being covariant over `T` *is*
281-
sound: it doesn't let you modify the value, only look at it. Without any way to
282-
mutate, there's no way for us to mess with any details. We can also see why
283-
`UnsafeCell` and all the other interior mutability types must be invariant: they
284-
make `&T` work like `&mut T`!
285-
286-
Now what about the lifetime on references? Why is it ok for both kinds of references
287-
to be covariant over their lifetimes? Well, here's a two-pronged argument:
288-
289-
First and foremost, subtyping references based on their lifetimes is *the entire point
290-
of subtyping in Rust*. The only reason we have subtyping is so we can pass
291-
long-lived things where short-lived things are expected. So it better work!
292-
293-
Second, and more seriously, lifetimes are only a part of the reference itself. The
294-
type of the referent is shared knowledge, which is why adjusting that type in only
295-
one place (the reference) can lead to issues. But if you shrink down a reference's
296-
lifetime when you hand it to someone, that lifetime information isn't shared in
297-
any way. There are now two independent references with independent lifetimes.
298-
There's no way to mess with the original reference's lifetime using the other one.
299-
300-
Or rather, the only way to mess with someone's lifetime is to build a meowing dog.
301-
But as soon as you try to build a meowing dog, the lifetime should be wrapped up
302-
in an invariant type, preventing the lifetime from being shrunk. To understand this
303-
better, let's port the meowing dog problem over to real Rust.
304-
305-
In the meowing dog problem we take a subtype (Cat), convert it into a supertype
306-
(Animal), and then use that fact to overwrite the subtype with a value that satisfies
307-
the constraints of the supertype but not the subtype (Dog).
308-
309-
So with lifetimes, we want to take a long-lived thing, convert it into a
310-
short-lived thing, and then use that to write something that doesn't live long
311-
enough into the place expecting something long-lived.
312-
313-
Here it is:
314-
315-
316-
The other argument is only an `&'a str`, which *is* covariant over `'a`. So the compiler
317-
adopts a constraint: `&'spike_str str` must be a subtype of `&'static str` (inclusive),
318-
which in turn implies `'spike_str` must be a subtype of `'static` (inclusive). Which is to say,
319-
`'spike_str` must contain `'static`. But only one thing contains `'static` -- `'static` itself!
320-
321-
This is why we get an error when we try to assign `&spike` to `spike_str`. The
322-
compiler has worked backwards to conclude `spike_str` must live forever, and `&spike`
323-
simply can't live that long.
324-
325-
So even though references are covariant over their lifetimes, they "inherit" invariance
326-
whenever they're put into a context that could do something bad with that. In this case,
327-
we inherited invariance as soon as we put our reference inside an `&mut T`.
328-
329-
As it turns out, the argument for why it's ok for Box (and Vec, Hashmap, etc.) to
330-
be covariant is pretty similar to the argument for why it's ok for
331-
references to be covariant: as soon as you try to stuff them in something like a
332-
mutable reference, they inherit invariance and you're prevented from doing anything
333-
bad.
334-
335-
However, Box makes it easier to focus on the by-value aspect of references that we
336-
partially glossed over.
337-
338-
Unlike a lot of languages which allow values to be freely aliased at all times,
339-
Rust has a very strict rule: if you're allowed to mutate or move a value, you
340-
are guaranteed to be the only one with access to it.
341-
342-
Consider the following code:
343-
344-
<!-- ignore: simplified code -->
345-
```rust,ignore
346-
let mr_snuggles: Box<Cat> = ..;
347-
let spike: Box<Dog> = ..;
348-
349-
let mut pet: Box<Animal>;
350-
pet = mr_snuggles;
351-
pet = spike;
352-
```
353-
354-
There is no problem at all with the fact that we have forgotten that `mr_snuggles` was a Cat,
355-
or that we overwrote him with a Dog, because as soon as we moved mr_snuggles to a variable
356-
that only knew he was an Animal, **we destroyed the only thing in the universe that
357-
remembered he was a Cat**!
358-
359-
In contrast to the argument about immutable references being soundly covariant because they
360-
don't let you change anything, owned values can be covariant because they make you
361-
change *everything*. There is no connection between old locations and new locations.
362-
Applying by-value subtyping is an irreversible act of knowledge destruction, and
363-
without any memory of how things used to be, no one can be tricked into acting on
364-
that old information!
244+
`Box<T>` is also *covariant* over `T`. This would make sense, since it's supposed to be
245+
usable the same as `&T`. If you try to mutate the box, you'll need a `&mut Box<T>` and the
246+
invariance of `&mut` will kick in here.
365247

366248
Only one thing left to explain: function pointers.
367249

368250
To see why `fn(T) -> U` should be covariant over `U`, consider the following signature:
369251

370252
<!-- ignore: simplified code -->
371253
```rust,ignore
372-
fn get_animal() -> Animal;
254+
fn get_str() -> &'a str;
373255
```
374256

375-
This function claims to produce an Animal. As such, it is perfectly valid to
257+
This function claims to produce a `str` bound by some liftime `'a`. As such, it is perfectly valid to
376258
provide a function with the following signature instead:
377259

378260
<!-- ignore: simplified code -->
379261
```rust,ignore
380-
fn get_animal() -> Cat;
262+
fn get_static() -> &'static str;
381263
```
382264

383-
After all, Cats are Animals, so always producing a Cat is a perfectly valid way
384-
to produce Animals. Or to relate it back to real Rust: if we need a function
385-
that is supposed to produce something that lives for `'short`, it's perfectly
386-
fine for it to produce something that lives for `'long`. We don't care, we can
387-
just forget that fact.
265+
So when the function is called, all it's expecting is a `&str` which lives at least the lifetime of `'a`,
266+
it doesn't matter if the value actually lives longer.
388267

389268
However, the same logic does not apply to *arguments*. Consider trying to satisfy:
390269

391270
<!-- ignore: simplified code -->
392271
```rust,ignore
393-
fn handle_animal(Animal);
272+
fn store_ref(&'a str);
394273
```
395274

396275
with:
397276

398277
<!-- ignore: simplified code -->
399278
```rust,ignore
400-
fn handle_animal(Cat);
279+
fn store_static(&'static str);
401280
```
402281

403-
The first function can accept Dogs, but the second function absolutely can't.
282+
The first function can accept any string reference as long as it lives at least for `'a`,
283+
but the second cannot accept a string reference that lives for any duration less than `'static`,
284+
which would cause a conflict.
404285
Covariance doesn't work here. But if we flip it around, it actually *does*
405-
work! If we need a function that can handle Cats, a function that can handle *any*
406-
Animal will surely work fine. Or to relate it back to real Rust: if we need a
407-
function that can handle anything that lives for at least `'long`, it's perfectly
408-
fine for it to be able to handle anything that lives for at least `'short`.
286+
work! If we need a function that can handle `&'static str`, a function that can handle *any* reference lifetime
287+
will surely work fine.
409288

410289
And that's why function types, unlike anything else in the language, are
411290
**contra**variant over their arguments.

0 commit comments

Comments
 (0)