diff options
Diffstat (limited to 'src/doc/nomicon')
-rw-r--r-- | src/doc/nomicon/src/phantom-data.md | 24 | ||||
-rw-r--r-- | src/doc/nomicon/src/subtyping.md | 474 |
2 files changed, 211 insertions, 287 deletions
diff --git a/src/doc/nomicon/src/phantom-data.md b/src/doc/nomicon/src/phantom-data.md index ca1c2c21c..449d9e774 100644 --- a/src/doc/nomicon/src/phantom-data.md +++ b/src/doc/nomicon/src/phantom-data.md @@ -24,7 +24,7 @@ We do this using `PhantomData`, which is a special marker type. `PhantomData` consumes no space, but simulates a field of the given type for the purpose of static analysis. This was deemed to be less error-prone than explicitly telling the type-system the kind of variance that you want, while also providing other -useful things such as the information needed by drop check. +useful things such as auto traits and the information needed by drop check. Iter logically contains a bunch of `&'a T`s, so this is exactly what we tell the `PhantomData` to simulate: @@ -234,14 +234,14 @@ standard library made a utility for itself called `Unique<T>` which: Here’s a table of all the wonderful ways `PhantomData` could be used: -| Phantom type | `'a` | `T` | -|-----------------------------|-----------|---------------------------| -| `PhantomData<T>` | - | covariant (with drop check) | -| `PhantomData<&'a T>` | covariant | covariant | -| `PhantomData<&'a mut T>` | covariant | invariant | -| `PhantomData<*const T>` | - | covariant | -| `PhantomData<*mut T>` | - | invariant | -| `PhantomData<fn(T)>` | - | contravariant | -| `PhantomData<fn() -> T>` | - | covariant | -| `PhantomData<fn(T) -> T>` | - | invariant | -| `PhantomData<Cell<&'a ()>>` | invariant | - | +| Phantom type | `'a` | `T` | `Send` | `Sync` | +|-----------------------------|-----------|-----------------------------|-----------|-----------| +| `PhantomData<T>` | - | covariant (with drop check) | `T: Send` | `T: Sync` | +| `PhantomData<&'a T>` | covariant | covariant | `T: Sync` | `T: Sync` | +| `PhantomData<&'a mut T>` | covariant | invariant | `T: Send` | `T: Sync` | +| `PhantomData<*const T>` | - | covariant | - | - | +| `PhantomData<*mut T>` | - | invariant | - | - | +| `PhantomData<fn(T)>` | - | contravariant | `Send` | `Sync` | +| `PhantomData<fn() -> T>` | - | covariant | `Send` | `Sync` | +| `PhantomData<fn(T) -> T>` | - | invariant | `Send` | `Sync` | +| `PhantomData<Cell<&'a ()>>` | invariant | - | `Send` | - | diff --git a/src/doc/nomicon/src/subtyping.md b/src/doc/nomicon/src/subtyping.md index 79b29beb0..f63b532cc 100644 --- a/src/doc/nomicon/src/subtyping.md +++ b/src/doc/nomicon/src/subtyping.md @@ -1,189 +1,166 @@ # Subtyping and Variance -Subtyping is a relationship between types that allows statically typed -languages to be a bit more flexible and permissive. +Rust uses lifetimes to track the relationships between borrows and ownership. +However, a naive implementation of lifetimes would be either too restrictive, +or permit undefined behavior. -Subtyping in Rust is a bit different from subtyping in other languages. This -makes it harder to give simple examples, which is a problem since subtyping, -and especially variance, is already hard to understand properly. As in, -even compiler writers mess it up all the time. +In order to allow flexible usage of lifetimes +while also preventing their misuse, Rust uses **subtyping** and **variance**. -To keep things simple, this section will consider a small extension to the -Rust language that adds a new and simpler subtyping relationship. After -establishing concepts and issues under this simpler system, -we will then relate it back to how subtyping actually occurs in Rust. - -So here's our simple extension, *Objective Rust*, featuring three new types: +Let's start with an example. ```rust -trait Animal { - fn snuggle(&self); - fn eat(&mut self); -} - -trait Cat: Animal { - fn meow(&self); +// Note: debug expects two parameters with the *same* lifetime +fn debug<'a>(a: &'a str, b: &'a str) { + println!("a = {a:?} b = {b:?}"); } -trait Dog: Animal { - fn bark(&self); +fn main() { + let hello: &'static str = "hello"; + { + let world = String::from("world"); + let world = &world; // 'world has a shorter lifetime than 'static + debug(hello, world); + } } ``` -But unlike normal traits, we can use them as concrete and sized types, just like structs. - -Now, say we have a very simple function that takes an Animal, like this: +In a conservative implementation of lifetimes, since `hello` and `world` have different lifetimes, +we might see the following error: -<!-- ignore: simplified code --> -```rust,ignore -fn love(pet: Animal) { - pet.snuggle(); -} +```text +error[E0308]: mismatched types + --> src/main.rs:10:16 + | +10 | debug(hello, world); + | ^ + | | + | expected `&'static str`, found struct `&'world str` ``` -By default, static types must match *exactly* for a program to compile. As such, -this code won't compile: +This would be rather unfortunate. In this case, +what we want is to accept any type that lives *at least as long* as `'world`. +Let's try using subtyping with our lifetimes. -<!-- ignore: simplified code --> -```rust,ignore -let mr_snuggles: Cat = ...; -love(mr_snuggles); // ERROR: expected Animal, found Cat -``` +## Subtyping -Mr. Snuggles is a Cat, and Cats aren't *exactly* Animals, so we can't love him! 😿 +Subtyping is the idea that one type can be used in place of another. -This is annoying because Cats *are* Animals. They support every operation -an Animal supports, so intuitively `love` shouldn't care if we pass it a `Cat`. -We should be able to just **forget** the non-animal parts of our `Cat`, as they -aren't necessary to love it. +Let's define that `Sub` is a subtype of `Super` (we'll be using the notation `Sub <: Super` throughout this chapter). -This is exactly the problem that *subtyping* is intended to fix. Because Cats are just -Animals **and more**, we say Cat is a *subtype* of Animal (because Cats are a *subset* -of all the Animals). Equivalently, we say that Animal is a *supertype* of Cat. -With subtypes, we can tweak our overly strict static type system -with a simple rule: anywhere a value of type `T` is expected, we will also -accept values that are subtypes of `T`. +What this is suggesting to us is that the set of *requirements* that `Super` defines +are completely satisfied by `Sub`. `Sub` may then have more requirements. -Or more concretely: anywhere an Animal is expected, a Cat or Dog will also work. +Now, in order to use subtyping with lifetimes, we need to define the requirement of a lifetime: -As we will see throughout the rest of this section, subtyping is a lot more complicated -and subtle than this, but this simple rule is a very good 99% intuition. And unless you -write unsafe code, the compiler will automatically handle all the corner cases for you. +> `'a` defines a region of code. -But this is the Rustonomicon. We're writing unsafe code, so we need to understand how -this stuff really works, and how we can mess it up. +Now that we have a defined set of requirements for lifetimes, we can define how they relate to each other: -The core problem is that this rule, naively applied, will lead to *meowing Dogs*. That is, -we can convince someone that a Dog is actually a Cat. This completely destroys the fabric -of our static type system, making it worse than useless (and leading to Undefined Behavior). +> `'long <: 'short` if and only if `'long` defines a region of code that **completely contains** `'short`. -Here's a simple example of this happening when we apply subtyping in a completely naive -"find and replace" way. +`'long` may define a region larger than `'short`, but that still fits our definition. -<!-- ignore: simplified code --> -```rust,ignore -fn evil_feeder(pet: &mut Animal) { - let spike: Dog = ...; +> As we will see throughout the rest of this chapter, +subtyping is a lot more complicated and subtle than this, +but this simple rule is a very good 99% intuition. +And unless you write unsafe code, the compiler will automatically handle all the corner cases for you. + +> But this is the Rustonomicon. We're writing unsafe code, +so we need to understand how this stuff really works, and how we can mess it up. - // `pet` is an Animal, and Dog is a subtype of Animal, - // so this should be fine, right..? - *pet = spike; +Going back to our example above, we can say that `'static <: 'world`. +For now, let's also accept the idea that subtypes of lifetimes can be passed through references +(more on this in [Variance](#variance)), +_e.g._ `&'static str` is a subtype of `&'world str`, then we can "downgrade" `&'static str` into a `&'world str`. +With that, the example above will compile: + +```rust +fn debug<'a>(a: &'a str, b: &'a str) { + println!("a = {a:?} b = {b:?}"); } fn main() { - let mut mr_snuggles: Cat = ...; - evil_feeder(&mut mr_snuggles); // Replaces mr_snuggles with a Dog - mr_snuggles.meow(); // OH NO, MEOWING DOG! + let hello: &'static str = "hello"; + { + let world = String::from("world"); + let world = &world; // 'world has a shorter lifetime than 'static + debug(hello, world); // hello silently downgrades from `&'static str` into `&'world str` + } } ``` -Clearly, we need a more robust system than "find and replace". That system is *variance*, -which is a set of rules governing how subtyping should compose. Most importantly, variance -defines situations where subtyping should be disabled. - -But before we get into variance, let's take a quick peek at where subtyping actually occurs in -Rust: *lifetimes*! - -> NOTE: The typed-ness of lifetimes is a fairly arbitrary construct that some -> disagree with. However it simplifies our analysis to treat lifetimes -> and types uniformly. +## Variance -Lifetimes are just regions of code, and regions can be partially ordered with the *contains* -(outlives) relationship. Subtyping on lifetimes is in terms of that relationship: -if `'big: 'small` ("big contains small" or "big outlives small"), then `'big` is a subtype -of `'small`. This is a large source of confusion, because it seems backwards -to many: the bigger region is a *subtype* of the smaller region. But it makes -sense if you consider our Animal example: Cat is an Animal *and more*, -just as `'big` is `'small` *and more*. +Above, we glossed over the fact that `'static <: 'b` implied that `&'static T <: &'b T`. This uses a property known as _variance_. +It's not always as simple as this example, though. To understand that, let's try to extend this example a bit: -Put another way, if someone wants a reference that lives for `'small`, -usually what they actually mean is that they want a reference that lives -for *at least* `'small`. They don't actually care if the lifetimes match -exactly. So it should be ok for us to **forget** that something lives for -`'big` and only remember that it lives for `'small`. +```rust,compile_fail,E0597 +fn assign<T>(input: &mut T, val: T) { + *input = val; +} -The meowing dog problem for lifetimes will result in us being able to -store a short-lived reference in a place that expects a longer-lived one, -creating a dangling reference and letting us use-after-free. +fn main() { + let mut hello: &'static str = "hello"; + { + let world = String::from("world"); + assign(&mut hello, &world); + } + println!("{hello}"); // use after free 😿 +} +``` -It will be useful to note that `'static`, the forever lifetime, is a subtype of -every lifetime because by definition it outlives everything. We will be using -this relationship in later examples to keep them as simple as possible. +In `assign`, we are setting the `hello` reference to point to `world`. +But then `world` goes out of scope, before the later use of `hello` in the println! -With all that said, we still have no idea how to actually *use* subtyping of lifetimes, -because nothing ever has type `'a`. Lifetimes only occur as part of some larger type -like `&'a u32` or `IterMut<'a, u32>`. To apply lifetime subtyping, we need to know -how to compose subtyping. Once again, we need *variance*. +This is a classic use-after-free bug! -## Variance +Our first instinct might be to blame the `assign` impl, but there's really nothing wrong here. +It shouldn't be surprising that we might want to assign a `T` into a `T`. -Variance is where things get a bit complicated. +The problem is that we cannot assume that `&mut &'static str` and `&mut &'b str` are compatible. +This means that `&mut &'static str` **cannot** be a *subtype* of `&mut &'b str`, +even if `'static` is a subtype of `'b`. -Variance is a property that *type constructors* have with respect to their -arguments. A type constructor in Rust is any generic type with unbound arguments. -For instance `Vec` is a type constructor that takes a type `T` and returns -`Vec<T>`. `&` and `&mut` are type constructors that take two inputs: a -lifetime, and a type to point to. +Variance is the concept that Rust borrows to define relationships about subtypes through their generic parameters. -> NOTE: For convenience we will often refer to `F<T>` as a type constructor just so +> NOTE: For convenience we will define a generic type `F<T>` so > that we can easily talk about `T`. Hopefully this is clear in context. -A type constructor F's *variance* is how the subtyping of its inputs affects the +The type `F`'s *variance* is how the subtyping of its inputs affects the subtyping of its outputs. There are three kinds of variance in Rust. Given two types `Sub` and `Super`, where `Sub` is a subtype of `Super`: -* `F` is *covariant* if `F<Sub>` is a subtype of `F<Super>` (subtyping "passes through") -* `F` is *contravariant* if `F<Super>` is a subtype of `F<Sub>` (subtyping is "inverted") -* `F` is *invariant* otherwise (no subtyping relationship exists) +* `F` is **covariant** if `F<Sub>` is a subtype of `F<Super>` (the subtype property is passed through) +* `F` is **contravariant** if `F<Super>` is a subtype of `F<Sub>` (the subtype property is "inverted") +* `F` is **invariant** otherwise (no subtyping relationship exists) -If `F` has multiple type parameters, we can talk about the individual variances -by saying that, for example, `F<T, U>` is covariant over `T` and invariant over `U`. +If we remember from the above examples, +it was ok for us to treat `&'a T` as a subtype of `&'b T` if `'a <: 'b`, +therefore we can say that `&'a T` is *covariant* over `'a`. -It is very useful to keep in mind that covariance is, in practical terms, "the" -variance. Almost all consideration of variance is in terms of whether something -should be covariant or invariant. Actually witnessing contravariance is quite difficult -in Rust, though it does in fact exist. +Also, we saw that it was not ok for us to treat `&mut &'a U` as a subtype of `&mut &'b U`, +therefore we can say that `&mut T` is *invariant* over `T` -Here is a table of important variances which the rest of this section will be devoted -to trying to explain: +Here is a table of some other generic types and their variances: -| | | 'a | T | U | -|---|-----------------|:---------:|:-----------------:|:---------:| -| * | `&'a T ` | covariant | covariant | | -| * | `&'a mut T` | covariant | invariant | | -| * | `Box<T>` | | covariant | | -| | `Vec<T>` | | covariant | | -| * | `UnsafeCell<T>` | | invariant | | -| | `Cell<T>` | | invariant | | -| * | `fn(T) -> U` | | **contra**variant | covariant | -| | `*const T` | | covariant | | -| | `*mut T` | | invariant | | +| | 'a | T | U | +|-----------------|:---------:|:-----------------:|:---------:| +| `&'a T ` | covariant | covariant | | +| `&'a mut T` | covariant | invariant | | +| `Box<T>` | | covariant | | +| `Vec<T>` | | covariant | | +| `UnsafeCell<T>` | | invariant | | +| `Cell<T>` | | invariant | | +| `fn(T) -> U` | | **contra**variant | covariant | +| `*const T` | | covariant | | +| `*mut T` | | invariant | | -The types with \*'s are the ones we will be focusing on, as they are in -some sense "fundamental". All the others can be understood by analogy to the others: +Some of these can be explained simply in relation to the others: * `Vec<T>` and all other owning pointers and collections follow the same logic as `Box<T>` * `Cell<T>` and all other interior mutability types follow the same logic as `UnsafeCell<T>` +* `UnsafeCell<T>` having interior mutability gives it the same variance properties as `&mut T` * `*const T` follows the logic of `&T` * `*mut T` follows the logic of `&mut T` (or `UnsafeCell<T>`) @@ -197,116 +174,45 @@ For more types, see the ["Variance" section][variance-table] on the reference. > take references with specific lifetimes (as opposed to the usual "any lifetime", > which gets into higher rank lifetimes, which work independently of subtyping). -Ok, that's enough type theory! Let's try to apply the concept of variance to Rust -and look at some examples. - -First off, let's revisit the meowing dog example: - -<!-- ignore: simplified code --> -```rust,ignore -fn evil_feeder(pet: &mut Animal) { - let spike: Dog = ...; - - // `pet` is an Animal, and Dog is a subtype of Animal, - // so this should be fine, right..? - *pet = spike; -} +Now that we have some more formal understanding of variance, +let's go through some more examples in more detail. -fn main() { - let mut mr_snuggles: Cat = ...; - evil_feeder(&mut mr_snuggles); // Replaces mr_snuggles with a Dog - mr_snuggles.meow(); // OH NO, MEOWING DOG! -} -``` - -If we look at our table of variances, we see that `&mut T` is *invariant* over `T`. -As it turns out, this completely fixes the issue! With invariance, the fact that -Cat is a subtype of Animal doesn't matter; `&mut Cat` still won't be a subtype of -`&mut Animal`. The static type checker will then correctly stop us from passing -a Cat into `evil_feeder`. - -The soundness of subtyping is based on the idea that it's ok to forget unnecessary -details. But with references, there's always someone that remembers those details: -the value being referenced. That value expects those details to keep being true, -and may behave incorrectly if its expectations are violated. - -The problem with making `&mut T` covariant over `T` is that it gives us the power -to modify the original value *when we don't remember all of its constraints*. -And so, we can make someone have a Dog when they're certain they still have a Cat. - -With that established, we can easily see why `&T` being covariant over `T` *is* -sound: it doesn't let you modify the value, only look at it. Without any way to -mutate, there's no way for us to mess with any details. We can also see why -`UnsafeCell` and all the other interior mutability types must be invariant: they -make `&T` work like `&mut T`! - -Now what about the lifetime on references? Why is it ok for both kinds of references -to be covariant over their lifetimes? Well, here's a two-pronged argument: - -First and foremost, subtyping references based on their lifetimes is *the entire point -of subtyping in Rust*. The only reason we have subtyping is so we can pass -long-lived things where short-lived things are expected. So it better work! - -Second, and more seriously, lifetimes are only a part of the reference itself. The -type of the referent is shared knowledge, which is why adjusting that type in only -one place (the reference) can lead to issues. But if you shrink down a reference's -lifetime when you hand it to someone, that lifetime information isn't shared in -any way. There are now two independent references with independent lifetimes. -There's no way to mess with the original reference's lifetime using the other one. - -Or rather, the only way to mess with someone's lifetime is to build a meowing dog. -But as soon as you try to build a meowing dog, the lifetime should be wrapped up -in an invariant type, preventing the lifetime from being shrunk. To understand this -better, let's port the meowing dog problem over to real Rust. - -In the meowing dog problem we take a subtype (Cat), convert it into a supertype -(Animal), and then use that fact to overwrite the subtype with a value that satisfies -the constraints of the supertype but not the subtype (Dog). - -So with lifetimes, we want to take a long-lived thing, convert it into a -short-lived thing, and then use that to write something that doesn't live long -enough into the place expecting something long-lived. - -Here it is: - -```rust,compile_fail -fn evil_feeder<T>(input: &mut T, val: T) { +```rust,compile_fail,E0597 +fn assign<T>(input: &mut T, val: T) { *input = val; } fn main() { - let mut mr_snuggles: &'static str = "meow! :3"; // mr. snuggles forever!! + let mut hello: &'static str = "hello"; { - let spike = String::from("bark! >:V"); - let spike_str: &str = &spike; // Only lives for the block - evil_feeder(&mut mr_snuggles, spike_str); // EVIL! + let world = String::from("world"); + assign(&mut hello, &world); } - println!("{}", mr_snuggles); // Use after free? + println!("{hello}"); } ``` And what do we get when we run this? ```text -error[E0597]: `spike` does not live long enough - --> src/main.rs:9:31 +error[E0597]: `world` does not live long enough + --> src/main.rs:9:28 | -6 | let mut mr_snuggles: &'static str = "meow! :3"; // mr. snuggles forever!! - | ------------ type annotation requires that `spike` is borrowed for `'static` +6 | let mut hello: &'static str = "hello"; + | ------------ type annotation requires that `world` is borrowed for `'static` ... -9 | let spike_str: &str = &spike; // Only lives for the block - | ^^^^^^ borrowed value does not live long enough -10 | evil_feeder(&mut mr_snuggles, spike_str); // EVIL! -11 | } - | - `spike` dropped here while still borrowed +9 | assign(&mut hello, &world); + | ^^^^^^ borrowed value does not live long enough +10 | } + | - `world` dropped here while still borrowed ``` Good, it doesn't compile! Let's break down what's happening here in detail. -First let's look at the new `evil_feeder` function: +First let's look at the `assign` function: ```rust -fn evil_feeder<T>(input: &mut T, val: T) { +fn assign<T>(input: &mut T, val: T) { *input = val; } ``` @@ -315,60 +221,43 @@ All it does is take a mutable reference and a value and overwrite the referent w What's important about this function is that it creates a type equality constraint. It clearly says in its signature the referent and the value must be the *exact same* type. -Meanwhile, in the caller we pass in `&mut &'static str` and `&'spike_str str`. +Meanwhile, in the caller we pass in `&mut &'static str` and `&'world str`. Because `&mut T` is invariant over `T`, the compiler concludes it can't apply any subtyping to the first argument, and so `T` must be exactly `&'static str`. -The other argument is only an `&'a str`, which *is* covariant over `'a`. So the compiler -adopts a constraint: `&'spike_str str` must be a subtype of `&'static str` (inclusive), -which in turn implies `'spike_str` must be a subtype of `'static` (inclusive). Which is to say, -`'spike_str` must contain `'static`. But only one thing contains `'static` -- `'static` itself! +This is counter to the `&T` case: -This is why we get an error when we try to assign `&spike` to `spike_str`. The -compiler has worked backwards to conclude `spike_str` must live forever, and `&spike` -simply can't live that long. +```rust +fn debug<T: std::fmt::Debug>(a: T, b: T) { + println!("a = {a:?} b = {b:?}"); +} +``` -So even though references are covariant over their lifetimes, they "inherit" invariance -whenever they're put into a context that could do something bad with that. In this case, -we inherited invariance as soon as we put our reference inside an `&mut T`. +where similarly `a` and `b` must have the same type `T`. +But since `&'a T` *is* covariant over `'a`, we are allowed to perform subtyping. +So the compiler decides that `&'static str` can become `&'b str` if and only if +`&'static str` is a subtype of `&'b str`, which will hold if `'static <: 'b`. +This is true, so the compiler is happy to continue compiling this code. -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 -references 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. +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. -However, Box makes it easier to focus on the by-value aspect of references that we -partially glossed over. +However Box makes it easier to focus on the by-value aspect of references that we partially glossed over. -Unlike a lot of languages which allow values to be freely aliased at all times, -Rust has a very strict rule: if you're allowed to mutate or move a value, you -are guaranteed to be the only one with access to it. +Unlike a lot of languages which allow values to be freely aliased at all times, Rust has a very strict rule: if you're allowed to mutate or move a value, you are guaranteed to be the only one with access to it. Consider the following code: -<!-- ignore: simplified code --> ```rust,ignore -let mr_snuggles: Box<Cat> = ..; -let spike: Box<Dog> = ..; +let hello: Box<&'static str> = Box::new("hello"); -let mut pet: Box<Animal>; -pet = mr_snuggles; -pet = spike; +let mut world: Box<&'b str>; +world = hello; ``` -There is no problem at all with the fact that we have forgotten that `mr_snuggles` was a Cat, -or that we overwrote him with a Dog, because as soon as we moved mr_snuggles to a variable -that only knew he was an Animal, **we destroyed the only thing in the universe that -remembered he was a Cat**! - -In contrast to the argument about immutable references being soundly covariant because they -don't let you change anything, owned values can be covariant because they make you -change *everything*. There is no connection between old locations and new locations. -Applying by-value subtyping is an irreversible act of knowledge destruction, and -without any memory of how things used to be, no one can be tricked into acting on -that old information! +There is no problem at all with the fact that we have forgotten that `hello` was alive for `'static`, +because as soon as we moved `hello` to a variable that only knew it was alive for `'b`, +**we destroyed the only thing in the universe that remembered it lived for longer**! Only one thing left to explain: function pointers. @@ -376,43 +265,78 @@ To see why `fn(T) -> U` should be covariant over `U`, consider the following sig <!-- ignore: simplified code --> ```rust,ignore -fn get_animal() -> Animal; +fn get_str() -> &'a str; ``` -This function claims to produce an Animal. As such, it is perfectly valid to +This function claims to produce a `str` bound by some liftime `'a`. As such, it is perfectly valid to provide a function with the following signature instead: <!-- ignore: simplified code --> ```rust,ignore -fn get_animal() -> Cat; +fn get_static() -> &'static str; ``` -After all, Cats are Animals, so always producing a Cat is a perfectly valid way -to produce Animals. Or to relate it back to real Rust: if we need a function -that is supposed to produce something that lives for `'short`, it's perfectly -fine for it to produce something that lives for `'long`. We don't care, we can -just forget that fact. +So when the function is called, all it's expecting is a `&str` which lives at least the lifetime of `'a`, +it doesn't matter if the value actually lives longer. However, the same logic does not apply to *arguments*. Consider trying to satisfy: <!-- ignore: simplified code --> ```rust,ignore -fn handle_animal(Animal); +fn store_ref(&'a str); ``` with: <!-- ignore: simplified code --> ```rust,ignore -fn handle_animal(Cat); +fn store_static(&'static str); ``` -The first function can accept Dogs, but the second function absolutely can't. +The first function can accept any string reference as long as it lives at least for `'a`, +but the second cannot accept a string reference that lives for any duration less than `'static`, +which would cause a conflict. Covariance doesn't work here. But if we flip it around, it actually *does* -work! If we need a function that can handle Cats, a function that can handle *any* -Animal will surely work fine. Or to relate it back to real Rust: if we need a -function that can handle anything that lives for at least `'long`, it's perfectly -fine for it to be able to handle anything that lives for at least `'short`. +work! If we need a function that can handle `&'static str`, a function that can handle *any* reference lifetime +will surely work fine. + +Let's see this in practice + +```rust,compile_fail +# use std::cell::RefCell; +thread_local! { + pub static StaticVecs: RefCell<Vec<&'static str>> = RefCell::new(Vec::new()); +} + +/// saves the input given into a thread local `Vec<&'static str>` +fn store(input: &'static str) { + StaticVecs.with(|v| { + v.borrow_mut().push(input); + }) +} + +/// Calls the function with it's input (must have the same lifetime!) +fn demo<'a>(input: &'a str, f: fn(&'a str)) { + f(input); +} + +fn main() { + demo("hello", store); // "hello" is 'static. Can call `store` fine + + { + let smuggle = String::from("smuggle"); + + // `&smuggle` is not static. If we were to call `store` with `&smuggle`, + // we would have pushed an invalid lifetime into the `StaticVecs`. + // Therefore, `fn(&'static str)` cannot be a subtype of `fn(&'a str)` + demo(&smuggle, store); + } + + StaticVecs.with(|v| { + println!("{:?}", v.borrow()); // use after free 😿 + }); +} +``` And that's why function types, unlike anything else in the language, are **contra**variant over their arguments. |