diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 34f9edc9549d..40b5a50ef8ee 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -522,7 +522,9 @@ SPDX-License-Identifier: CC-BY-4.0
- [Branded pt 3: Implementation](idiomatic/leveraging-the-type-system/token-types/branded-03-impl.md)
- [Branded pt 4: Branded types in action.](idiomatic/leveraging-the-type-system/token-types/branded-04-in-action.md)
- [Polymorphism](idiomatic/polymorphism.md)
- - [Refresher](idiomatic/polymorphism/refresher.md)
+ - [What is Polymorphism?](idiomatic/polymorphism/python.md)
+ - [Kinds of Polymorphism](idiomatic/polymorphism/polymorphism-kinds.md)
+ - [Generics](idiomatic/polymorphism/generics.md)
- [Traits](idiomatic/polymorphism/refresher/traits.md)
- [Trait Bounds](idiomatic/polymorphism/refresher/trait-bounds.md)
- [Deriving Traits](idiomatic/polymorphism/refresher/deriving-traits.md)
@@ -533,22 +535,27 @@ SPDX-License-Identifier: CC-BY-4.0
- [Orphan Rule](idiomatic/polymorphism/refresher/orphan-rule.md)
- [Statically Sized and Dynamically Sized types](idiomatic/polymorphism/refresher/sized.md)
- [Monomorphization and Binary Size](idiomatic/polymorphism/refresher/monomorphization.md)
- - [From OOP to Rust](idiomatic/polymorphism/from-oop-to-rust.md)
- - [Inheritance](idiomatic/polymorphism/from-oop-to-rust/inheritance.md)
- - [Why no Inheritance in Rust?](idiomatic/polymorphism/from-oop-to-rust/why-no-inheritance.md)
- - [Inheritance from Rust's Perspective](idiomatic/polymorphism/from-oop-to-rust/switch-perspective.md)
- - ["Inheritance" in Rust and Supertraits](idiomatic/polymorphism/from-oop-to-rust/supertraits.md)
- - [Composition over Inheritance](idiomatic/polymorphism/from-oop-to-rust/composition.md)
- - [Trait Objects and Dynamic Dispatch](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-trait.md)
+ - [Sealed Traits](idiomatic/polymorphism/from-oop-to-rust/sealed-traits.md)
+ - [Traits for Polymorphism users can extend](idiomatic/polymorphism/from-oop-to-rust/sticking-with-traits.md)
+ - [Enums](idiomatic/polymorphism/enums.md)
+ - [Inspecting Enums](idiomatic/polymorphism/enums/inspecting.md)
+ - [Heterogeneous Collections](idiomatic/polymorphism/enums/heterogeneous.md)
+ - [Re-exposing Traits](idiomatic/polymorphism/enums/traits.md)
+ - [Sealing with Enums](idiomatic/polymorphism/from-oop-to-rust/sealing-with-enums.md)
+ - [`dyn`](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-trait.md)
- [Dyn Compatibility](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-compatible.md)
- [Generics vs Trait Objects](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-vs-generics.md)
- [Limits of Trait Objects](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/limits.md)
- [Heterogeneous Collections](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/heterogeneous.md)
- [The `Any` Trait](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/any-trait.md)
- [Pitfall: Reaching too quickly for `dyn Trait`](idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/pitfalls.md)
- - [Sealed Traits](idiomatic/polymorphism/from-oop-to-rust/sealed-traits.md)
- - [Sealing with Enums](idiomatic/polymorphism/from-oop-to-rust/sealing-with-enums.md)
- - [Traits for Polymorphism users can extend](idiomatic/polymorphism/from-oop-to-rust/sticking-with-traits.md)
+ - [Enums vs `dyn`](idiomatic/polymorphism/enum-vs-dyn.md)
+ - [From OOP to Rust](idiomatic/polymorphism/from-oop-to-rust.md)
+ - [Inheritance](idiomatic/polymorphism/from-oop-to-rust/inheritance.md)
+ - [Why no Inheritance in Rust?](idiomatic/polymorphism/from-oop-to-rust/why-no-inheritance.md)
+ - [Inheritance from Rust's Perspective](idiomatic/polymorphism/from-oop-to-rust/switch-perspective.md)
+ - ["Inheritance" in Rust and Supertraits](idiomatic/polymorphism/from-oop-to-rust/supertraits.md)
+ - [Composition over Inheritance](idiomatic/polymorphism/from-oop-to-rust/composition.md)
- [Problem solving: Break Down the Problem](idiomatic/polymorphism/from-oop-to-rust/problem-solving.md)
---
diff --git a/src/idiomatic/polymorphism/enum-vs-dyn.md b/src/idiomatic/polymorphism/enum-vs-dyn.md
new file mode 100644
index 000000000000..0756a20d14f8
--- /dev/null
+++ b/src/idiomatic/polymorphism/enum-vs-dyn.md
@@ -0,0 +1,22 @@
+# Enums vs `dyn`
+
+Enums and `dyn` provide two different approaches to dynamic polymorphism, and in
+many cases both approaches can be used to solve the same problem. The main
+trade-offs between the two are:
+
+- Enums make "downcasting" to a subtype easy via pattern matching. Downcasting
+ is possible with `dyn` but is more cumbersome.
+- `dyn` allows for downstream code to introduce new types, whereas enums do not.
+
+
+
+- When deciding whether to use an enum or `dyn`, there are two questions to ask:
+
+ - Do I need to be able to downcast to the concrete subtype? Or do I primarily
+ expect to go through a trait interface without needing to know the concrete
+ type?
+
+ - Do I know the full set of types up front, or do I need to allow downstream
+ code to extend the set of types I will be handling?
+
+
diff --git a/src/idiomatic/polymorphism/enums.md b/src/idiomatic/polymorphism/enums.md
new file mode 100644
index 000000000000..ec99be1f937e
--- /dev/null
+++ b/src/idiomatic/polymorphism/enums.md
@@ -0,0 +1,44 @@
+# Enums
+
+Sometimes we need to handle multiple different types at runtime. Enums are a
+powerful tool that allows us to safely and robustly describe situations like
+this.
+
+```rust,editable
+use std::collections::HashMap;
+
+fn main() {
+ let number = parse_json("123");
+ let array = parse_json("[456, true, false]");
+ let object = parse_json(r#"{ "key": "value" }"#);
+}
+
+fn parse_json(doc: &str) -> JsonValue {
+ todo!("Parse the JSON string...")
+}
+
+enum JsonValue {
+ Object(HashMap),
+ Array(Vec),
+ String(String),
+ Number(f64),
+ Bool(bool),
+ Null,
+}
+```
+
+
+
+- The other category of polymorphism is **dynamic polymorphism**, where we can
+ have different types of value at runtime, and we can't know statically which
+ type we'll have at any given time.
+
+- As an example, consider parsing a JSON string. There are several different
+ types of JSON value, and which one we return depends on the contents of the
+ input string.
+
+- Our `parse_json` function has to return a single, concrete type, and that type
+ needs to describe all of the possible types a JSON value can be. Enums are a
+ natural way of describing this kind of situation in Rust.
+
+
diff --git a/src/idiomatic/polymorphism/enums/heterogeneous.md b/src/idiomatic/polymorphism/enums/heterogeneous.md
new file mode 100644
index 000000000000..1236acc40427
--- /dev/null
+++ b/src/idiomatic/polymorphism/enums/heterogeneous.md
@@ -0,0 +1,50 @@
+# Heterogeneous Collections with Enums
+
+Enums give us a way to create collections that can store different types of
+element at runtime:
+
+```rust,editable
+struct Dog {
+ name: String,
+}
+
+struct Cat {
+ age: u8,
+}
+
+enum AnyPet {
+ Dog(Dog),
+ Cat(Cat),
+}
+
+fn main() {
+ let pets = vec![
+ AnyPet::Dog(Dog { name: "Fido".into() }),
+ AnyPet::Cat(Cat { age: 19 }),
+ ];
+}
+```
+
+
+
+- A common situation where we might need dynamic polymorphism is when we want to
+ store different types of value in the same collection.
+
+- In the above example, we want to store both `Cat`s and `Dog`s in the same
+ list. `Vec` doesn't support this directly: All elements of the `Vec` must be
+ the same type.
+
+- Wrapping our two different pet types into a single `AnyPet` enum gives us a
+ unified type representation that can be stored in a `Vec`, while allowing
+ individual elements of the `Vec` to be different types.
+
+- This requires that we know all possible pet types up front, as we need to
+ explicitly list them as different variants of the `AnyPet` enum. This works
+ well for libraries or applications that define the full set of possible types,
+ but does not allow downstream users to extend our list of pet types.
+
+- Later we will see that we can do the same thing with `dyn`, which allows
+ downstream extension at the cost of being harder to downcast and requiring
+ dynamic dispatch.
+
+
diff --git a/src/idiomatic/polymorphism/enums/inspecting.md b/src/idiomatic/polymorphism/enums/inspecting.md
new file mode 100644
index 000000000000..5562d816cf39
--- /dev/null
+++ b/src/idiomatic/polymorphism/enums/inspecting.md
@@ -0,0 +1,32 @@
+# Inspecting Enums
+
+We can easily inspect the contents of an enum using **pattern matching**:
+
+```rust,editable,compile_fail
+fn do_json_stuff(json: &str) {
+ match parse_json(json) {
+ JsonValue::Object(obj) => println!("We got an object: {obj:?}"),
+ JsonValue::Array(array) => println!("We got an array: {array:?}"),
+ JsonValue::String(string) => println!("We got a string: {string:?}"),
+ JsonValue::Number(num) => println!("We got a number: {num}"),
+ JsonValue::Bool(b) => println!("We got a bool: {b}"),
+ JsonValue::Null => println!("We got a null"),
+ }
+}
+```
+
+
+
+- Continuing with our JSON parsing example, we can easily determine which kind
+ of value we got by pattern matching on the resulting enum.
+
+- This makes enums a good fit for scenarios where we want to handle different
+ types at runtime, but want to retain type information and the ability to
+ directly inspect the concrete value.
+
+- This is a big advantage enums have over `dyn`: With `dyn` we can't easily
+ downcast to the specific concrete type, and are generally restricted to going
+ through the trait interface. Later we'll see that we can support downcasting
+ with `dyn`, but doing so requires extra setup that isn't necessary with enums.
+
+
diff --git a/src/idiomatic/polymorphism/enums/traits.md b/src/idiomatic/polymorphism/enums/traits.md
new file mode 100644
index 000000000000..620059422517
--- /dev/null
+++ b/src/idiomatic/polymorphism/enums/traits.md
@@ -0,0 +1,90 @@
+# Re-exposing Traits
+
+Sometimes we use an enum to abstract over multiple types that implement the same
+trait, and want the enum to also re-expose the trait's interface.
+
+```rust,editable
+trait Pet {
+ fn talk(&self);
+}
+
+struct Dog {
+ name: String,
+}
+
+struct Cat {
+ age: u8,
+}
+
+impl Pet for Dog {
+ fn talk(&self) {
+ println!("Woof! My name is {}~!", self.name);
+ }
+}
+
+impl Pet for Cat {
+ fn talk(&self) {
+ println!("Meow! I am {} years old", self.age);
+ }
+}
+
+enum AnyPet {
+ Dog(Dog),
+ Cat(Cat),
+}
+
+impl Pet for AnyPet {
+ fn talk(&self) {
+ match self {
+ Self::Dog(dog) => dog.talk(),
+ Self::Cat(cat) => cat.talk(),
+ }
+ }
+}
+
+fn do_pet_stuff(pet: &impl Pet) {
+ pet.talk();
+}
+
+fn main() {
+ let cat = Cat {
+ age: 19,
+ };
+
+ let dog = Dog {
+ name: "Fido".into(),
+ };
+
+ do_pet_stuff(&cat);
+ do_pet_stuff(&dog);
+ do_pet_stuff(&AnyPet::Dog(dog));
+ do_pet_stuff(&AnyPet::Cat(cat));
+}
+```
+
+
+
+- One drawback of using an enum for dynamic polymorphism is that if our
+ underlying types (`Cat` and `Dog` in this case) implement a trait (`Pet`), our
+ wrapper enum doesn't automatically expose that same trait interface.
+
+- We can generally implement the trait for the wrapper enum by matching on the
+ enum and dispatching to the corresponding trait method on the underlying
+ types.
+
+- This is dynamic dispatch, but using the enum's discriminant instead of a
+ vtable in order to lookup the correct function to call.
+
+- This is an ergonomic drawback of an enum vs `dyn`: The implementation of `Pet`
+ for `AnyPet` is pure boilerplate that we need to repeat each time we have a
+ situation like this, whereas `dyn` gives us this behavior purely from the
+ `Pet` impls on `Cat` and `Dog`.
+
+- The advantage of this approach is that we retain the useful properties of an
+ enum (e.g. the ability to pattern match on it) while also exposing a way to do
+ dynamic dispatch through the trait's interface.
+
+- This also enables us to use `AnyPet` with generic functions like
+ `do_pet_stuff`, which we can also do with `dyn`.
+
+
diff --git a/src/idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-trait.md b/src/idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-trait.md
index 9467d88de6eb..a9a4344bddc3 100644
--- a/src/idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-trait.md
+++ b/src/idiomatic/polymorphism/from-oop-to-rust/dynamic-dispatch/dyn-trait.md
@@ -26,14 +26,8 @@ fn main() {
-- Dynamic Dispatch is a tool in Object Oriented Programming that is often used
- in places where one needs to care more about the behavior of a type than what
- the type is.
-
- In OOP languages, dynamic dispatch is often an _implicit_ process and not
- something you can opt out of.
-
- In Rust, we use `dyn Trait`: an opt-in form of dynamic dispatch.
+- Our other main mechanism of doing dynamic polymorphism is `dyn`, which gives
+ us dynamic dispatch through a trait interface.
- For any trait that is _dyn compatible_ we can coerce a reference to a value of
that trait into a `dyn Trait` value.
diff --git a/src/idiomatic/polymorphism/generics.md b/src/idiomatic/polymorphism/generics.md
new file mode 100644
index 000000000000..e284d37504c2
--- /dev/null
+++ b/src/idiomatic/polymorphism/generics.md
@@ -0,0 +1,41 @@
+# Generics
+
+Generics are used when we want to abstract over types, but we expect the users
+of our code to know the concrete types.
+
+```rust
+pub struct Vec { ... }
+```
+
+```rust,editable
+let ints: Vec = Vec::new();
+vec.push(123);
+vec.push(456);
+
+let strings: Vec<&str> = Vec::new();
+vec.push("hello");
+vec.push("goodbye");
+```
+
+
+
+- Generics are our mechanism for **static polymorphism**, which is polymorphism
+ where the types are fully known at compile time.
+
+- One example of this is `Vec`, which is generic over the type of element it
+ stores. `Vec` itself is polymorphic: It's written in such a way that it
+ doesn't know what type of element will be stored in it. But in order to use a
+ `Vec`, you must specify a concrete type to use for the element.
+
+- Generics are a mechanism for **code reuse**: You have some common logic that
+ is fundamentally the same regardless of what specific type it handles, and
+ generics give you a way to abstract over those different types **without
+ duplicating logic**.
+
+- Note that there's no dynamism: The types must be fully known at compile time,
+ there's no way to select a type for `Vec`'s element at runtime. This means
+ that generics are not an option when we need **runtime polymorphism**. Later
+ we will look at two mechanisms for doing dynamic polymorphism: Enums and
+ `dyn`.
+
+
diff --git a/src/idiomatic/polymorphism/polymorphism-kinds.md b/src/idiomatic/polymorphism/polymorphism-kinds.md
new file mode 100644
index 000000000000..cdd53f7aac29
--- /dev/null
+++ b/src/idiomatic/polymorphism/polymorphism-kinds.md
@@ -0,0 +1,31 @@
+# Kinds of Polymorphism
+
+In Rust we have 3 different mechanism for doing polymorphism:
+
+- **Generics** - Static polymorphism where code abstracts over types but the
+ types are fully known at compile time.
+- **Enums** - Dynamic polymorphism where a fixed set of known types are selected
+ between at runtime.
+- **`dyn`** - Dynamic polymorphism where any type meeting a particular trait
+ interface can be used.
+
+
+
+- When discussing polymorphism in Rust, it's helpful to differentiate between
+ **static** polymorphism and **dynamic** polymorphism.
+
+ - **static polymorphism** is when we abstract over types, but the type
+ information is fully known by the compiler. This allows us to reuse code in
+ different type contexts without needing to add any runtime overhead, and we
+ have access to the full set of features that traits expose. This is
+ accomplished with **generics** in Rust.
+
+ - **dynamic polymorphism** is when we need to select between different types
+ at runtime, and we don't know at compile time which specific type will be
+ used. When we have a fixed set of known types to choose from, we can use
+ **enums** to track at runtime which one we have. When we don't know ahead of
+ time which types may be used, e.g. if downstream users may introduce new
+ types that we don't know about, then we use **`dyn`** to allow
+ extensibility.
+
+
diff --git a/src/idiomatic/polymorphism/python.md b/src/idiomatic/polymorphism/python.md
new file mode 100644
index 000000000000..6fda526d1704
--- /dev/null
+++ b/src/idiomatic/polymorphism/python.md
@@ -0,0 +1,55 @@
+# What is Polymorphism?
+
+In a dynamic language like Python, we don't have to do anything special to allow
+a function to accept arguments of different types:
+
+```python,editable
+def print_value(val):
+ print(val)
+
+print_value(123)
+print_value("hello")
+print_value({})
+```
+
+But Rust's type system is extremely static, meaning that by default a function's
+arguments are limited to exactly the type declared in the function signature:
+
+```rust,editable
+fn print_value(val: i32) {
+ println!("{}", val);
+}
+
+fn main() {
+ print_value(123);
+ // print_value("hello"); // 🛠️❌ Mismatched types!
+}
+```
+
+
+
+- If you are coming from a dynamic language like Python, the concept of
+ polymorphism may be new to you because in dynamic languages everything is
+ inherently polymorphic. But Rust is a very statically-typed language, meaning
+ the compiler heavily restricts what types can be used where.
+
+- Static typing is a powerful tool that allows the compiler to enforce correct
+ usage of our APIs: If your function needs to be given an `i32` in order to
+ function correctly, the compiler won't allow a user of that function to pass
+ in a string.
+
+- But static typing is restrictive in cases where we want to write code that is
+ flexible in its handling of types. In the example on this slide, we might want
+ our `print_value` function to be able to print both numbers and strings, but
+ as written it can only accept `i32` values.
+
+- Edit `print_value` to make it generic using `Display` to print different kinds
+ of values:
+
+ ```rust
+ fn print_value(val: impl std::fmt::Display) {
+ println!("{}", val);
+ }
+ ```
+
+
diff --git a/src/idiomatic/polymorphism/refresher/supertraits.md b/src/idiomatic/polymorphism/refresher/supertraits.md
index 50e27a1291db..aadd5d3dbb84 100644
--- a/src/idiomatic/polymorphism/refresher/supertraits.md
+++ b/src/idiomatic/polymorphism/refresher/supertraits.md
@@ -32,8 +32,8 @@ pub trait Ord: Eq + PartialOrd {
-- When authoring a trait, you can specify traits that a type must also. These
- are called _supertraits_.
+- When authoring a trait, you can specify traits that a type must also
+ implement. These are called _supertraits_.
For the example above, any type that implements `Mammal` must also implement
`Animal`.