diff --git a/src/subtyping.md b/src/subtyping.md index b2ceb7b..e59e8a0 100644 --- a/src/subtyping.md +++ b/src/subtyping.md @@ -9,7 +9,7 @@ while also preventing their misuse, Rust uses a combination of **Subtyping** and ## Subtyping -Subtyping is the idea that one type can be a *subtype* of another. +Subtyping is the idea that one type can be used in place of another. Let's define that `Sub` is a subtype of `Super` (we'll be using the notation `Sub: Super` throughout this chapter) @@ -21,15 +21,15 @@ An example of simple subtyping that exists in the language are [supertraits](htt ```rust use std::fmt; -trait OutlinePrint: fmt::Display { - fn outline_print(&self) { - todo!() - } +pub trait Error: fmt::Display { + fn source(&self) -> Option<&(dyn Error + 'static)>; + fn description(&self) -> &str; + fn cause(&self) -> Option<&dyn Error>; } ``` -Here, we have that `OutlinePrint: fmt::Display` (`OutlinePrint` is a *subtype* of `Display`), -because it has all the requirements of `fmt::Display`, plus the `outline_print` function. +Here, we have that `Error: fmt::Display` (`Error` is a *subtype* of `Display`), +because it has all the requirements of `fmt::Display`, plus the `source`/`description`/`cause` functions. However, subtyping in traits is not that interesting in the case of Rust. Here in the nomicon, we're going to focus more with how subtyping interacts with **lifetimes** @@ -51,7 +51,7 @@ fn main() { } ``` -In an overly restrictive implementation of lifetimes, since `a` and `b` have differeing lifetimes, +In a conservative implementation of lifetimes, since `a` and `b` have differeing lifetimes, we might see the following error: ```text @@ -64,10 +64,12 @@ error[E0308]: mismatched types | expected `&'static str`, found struct `&'b str` ``` -This is over-restrictive. In this case, what we want is to accept any type that lives *at least as long* as `'b`. +This would be rather unfortunate. In this case, +what we want is to accept any type that lives *at least as long* as `'b`. Let's try using subtyping with our lifetimes. -Let's define a lifetime to have the a simple set of requirements: `'a` defines a region of code in which a value will be alive. +Let's define a lifetime to have the a simple set of requirements: +`'a` defines a region of code in which a value will be alive. Now that we have a defined set of requirements for lifetimes, we can define how they relate to each other. `'a: 'b` if and only if `'a` defines a region of code that **completely contains** `'b`. @@ -108,20 +110,24 @@ fn main() { let world = String::from("world"); assign(&mut hello, &world); } + println!("{}", hello); } ``` -If this were to compile, this would have a memory bug. +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! + +This is a classic use-after-free bug! -If we were to expand this out, we'd see that we're trying to assign a `&'b str` into a `&'static str`, -but the problem is that as soon as `b` goes out of scope, `a` is now invalid, even though it's supposed to have a `'static` lifetime. +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`. -However, the implementation of `assign` is valid. -Therefore, this must mean that `&mut &'static str` should **not** a *subtype* of `&mut &'b str`, +The problem is that we cannot assume that `&mut &'static str` and `&mut &'b str` are compatible. +This must mean that `&mut &'static str` should **not** be a *subtype* of `&mut &'b str`, even if `'static` is a subtype of `'b`. Variance is the way that Rust defines the relationships of subtypes through their *type constructor*. -A type constructor in Rust is any generic type with unbound arguments. +A type constructor is any generic type with unbound arguments. For instance `Vec` is a type constructor that takes a type `T` and returns `Vec`. `&` and `&mut` are type constructors that take two inputs: a lifetime, and a type to point to. @@ -190,6 +196,7 @@ fn main() { let world = String::from("world"); assign(&mut hello, &world); } + println!("{}", hello); } ```