From 4a15be206eb48eb8d3125d040f0a5118956edf4f Mon Sep 17 00:00:00 2001 From: Quentin Santos Date: Wed, 29 Nov 2023 13:31:21 +0100 Subject: [PATCH 1/2] Rename generic type parameter in Subtyping chapter `T` was used to refer both to the generic type parameter of the function used as an example, and to the type that this generic type parameter would be a reference to. To make things clearer, the generic type parameter is renamed to `R`, since it is intended to be a reference in the examples. --- src/subtyping.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/subtyping.md b/src/subtyping.md index f63b532c..3a548125 100644 --- a/src/subtyping.md +++ b/src/subtyping.md @@ -93,10 +93,14 @@ fn main() { ## Variance 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: +It's not always as simple as this example, though. To understand that, let's try to extend this example a bit. + +For the purpose of demonstration, we will illustrate lifetimes using references. +For example, the type `&'a T` will allow us to talk about lifetime `'a`. +We will use `R` for generic type parameters that are meant to refer to some reference and associated lifetime. ```rust,compile_fail,E0597 -fn assign(input: &mut T, val: T) { +fn assign(input: &mut R, val: R) { *input = val; } @@ -104,7 +108,7 @@ fn main() { let mut hello: &'static str = "hello"; { let world = String::from("world"); - assign(&mut hello, &world); + assign(&mut hello, &world); // R should bind to &str; but we need a lifetime too } println!("{hello}"); // use after free 😿 } @@ -116,11 +120,12 @@ But then `world` goes out of scope, before the later use of `hello` in the print This is a classic use-after-free bug! 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`. +It shouldn't be surprising that we might want to assign a `R` into a `R`. 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`. +`'static` **is** a subtype of `'b`. +Similarly, `R1 = &'static` **is** a subtype of `R2 = &'b`. +However, `&mut R1` **cannot** be a *subtype* of `&mut R2`, because `R1 ≠ R2`. Variance is the concept that Rust borrows to define relationships about subtypes through their generic parameters. @@ -139,7 +144,8 @@ 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`. -Also, we saw that it was not ok for us to treat `&mut &'a U` as a subtype of `&mut &'b U`, +Also, we saw that it was not ok for us to treat `&mut T1` as a subtype of `&mut T2` +(for instance, with `T1 = &'a U` and `T2 = &'b U`), therefore we can say that `&mut T` is *invariant* over `T` Here is a table of some other generic types and their variances: @@ -178,7 +184,7 @@ Now that we have some more formal understanding of variance, let's go through some more examples in more detail. ```rust,compile_fail,E0597 -fn assign(input: &mut T, val: T) { +fn assign(input: &mut R, val: R) { *input = val; } @@ -212,7 +218,7 @@ Good, it doesn't compile! Let's break down what's happening here in detail. First let's look at the `assign` function: ```rust -fn assign(input: &mut T, val: T) { +fn assign(input: &mut R, val: R) { *input = val; } ``` @@ -223,22 +229,22 @@ clearly says in its signature the referent and the value must be the *exact same 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`. +Because `&mut R` is invariant over `R`, the compiler concludes it can't apply any subtyping +to the first argument, and so `R` must be exactly `&'static str`. This is counter to the `&T` case: ```rust -fn debug(a: T, b: T) { +fn debug(a: R, b: R) { println!("a = {a:?} b = {b:?}"); } ``` -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. +where similarly `a` and `b` must have the same type `R`. +But since `&'a T` *is* covariant over `'a`, we are allowed to perform subtyping: +`'static` is a sub-type `'b`, so `&'static str` is a subtype of `&'b str`. +So we can bind `R` to `&'b str`, since it works for both arguments of `debug`. +With this, 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 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. From 4a842fedabb36e20d6805088646400cff6dede43 Mon Sep 17 00:00:00 2001 From: Quentin Santos Date: Wed, 29 Nov 2023 13:43:19 +0100 Subject: [PATCH 2/2] Fix typo --- src/subtyping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subtyping.md b/src/subtyping.md index 3a548125..b8d46835 100644 --- a/src/subtyping.md +++ b/src/subtyping.md @@ -274,7 +274,7 @@ To see why `fn(T) -> U` should be covariant over `U`, consider the following sig fn get_str() -> &'a str; ``` -This function claims to produce a `str` bound by some liftime `'a`. As such, it is perfectly valid to +This function claims to produce a `str` bound by some lifetime `'a`. As such, it is perfectly valid to provide a function with the following signature instead: