From be796c36813c5b2f33015c518248ff1905e16d2f Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Tue, 8 Jul 2025 17:02:42 +0200
Subject: [PATCH 01/16] Extension traits
---
src/SUMMARY.md | 4 +
.../extension-traits.md | 37 +++++++++
.../extending-foreign-traits.md | 7 ++
.../extending-foreign-types.md | 80 +++++++++++++++++++
.../method-resolution-conflicts.md | 79 ++++++++++++++++++
5 files changed, 207 insertions(+)
create mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits.md
create mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md
create mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
create mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 1950476a423a..bc121993d98a 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -437,6 +437,10 @@
- [Semantic Confusion](idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md)
- [Parse, Don't Validate](idiomatic/leveraging-the-type-system/newtype-pattern/parse-don-t-validate.md)
- [Is It Encapsulated?](idiomatic/leveraging-the-type-system/newtype-pattern/is-it-encapsulated.md)
+ - [Extension Traits](idiomatic/leveraging-the-type-system/extension-traits.md)
+ - [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md)
+ - [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md)
+ - [Extending Foreign Traits](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md)
---
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits.md
new file mode 100644
index 000000000000..de44b5ba1b0d
--- /dev/null
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits.md
@@ -0,0 +1,37 @@
+---
+minutes: 5
+---
+
+# Extension Traits
+
+In Rust, you can't define new inherent methods for foreign types.
+
+```rust,compile_fail
+// 🛠️❌
+impl &'_ str {
+ pub fn is_palindrome(&self) -> bool {
+ self.chars().eq(self.chars().rev())
+ }
+}
+```
+
+You can use the **extension trait pattern** to work around this limitation.
+
+
+
+- Try to compile the example to show the compiler error that's emitted.
+
+ Point out, in particular, how the compiler error message nudges you towards
+ the extension trait pattern.
+
+- Explain how many type-system restrictions in Rust aim to prevent _ambiguity_.
+
+ If you were allowed to define new inherent methods on foreign types, there
+ would need to be a mechanism to disambiguate between distinct inherent methods
+ with the same name.
+
+ In particular, adding a new inherent method to a library type could cause
+ errors in downstream code if the name of the new method conflicts with an
+ inherent method that's been defined in the consuming crate.
+
+
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md
new file mode 100644
index 000000000000..a56446f4c2e4
--- /dev/null
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md
@@ -0,0 +1,7 @@
+# Extending Foreign Traits
+
+- TODO: Show how extension traits can be used to extend traits rather than
+ types.
+- TODO: Show disambiguation syntax for naming conflicts between trait methods
+ and extension trait methods.
+- https://github.com/rust-lang/rfcs/blob/master/text/0132-ufcs.md
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
new file mode 100644
index 000000000000..99a9f71d9342
--- /dev/null
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
@@ -0,0 +1,80 @@
+---
+minutes: 15
+---
+
+# Extending Foreign Types
+
+An **extension trait** is a local trait definition whose primary purpose is to
+attach new methods to foreign types.
+
+```rust
+mod ext {
+ pub trait StrExt {
+ fn is_palindrome(&self) -> bool;
+ }
+
+ impl StrExt for &str {
+ fn is_palindrome(&self) -> bool {
+ self.chars().eq(self.chars().rev())
+ }
+ }
+}
+
+// Bring the extension trait into scope..
+pub use ext::StrExt as _;
+// ..then invoke its methods as if they were inherent methods
+assert!("dad".is_palindrome());
+assert!(!"grandma".is_palindrome());
+```
+
+
+
+- The `Ext` suffix is conventionally attached to the name of extension traits.
+
+ It communicates that the trait is primarily used for extension purposes, and
+ it is therefore not intended to be implemented outside the crate that defines
+ it.
+
+ Refer to the ["Extension Trait" RFC][1] as the authoritative source for naming
+ conventions.
+
+- The trait implementation for the chosen foreign type must belong to the same
+ crate where the trait is defined, otherwise you'll be blocked by Rust's
+ [_orphan rule_][2].
+
+- The extension trait must be in scope when its methods are invoked.
+
+ Comment out the `use` statement in the example to show the compiler error
+ that's emitted if you try to invoke an extension method without having the
+ corresponding extension trait in scope.
+
+- The `as _` syntax reduces the likelihood of naming conflicts when multiple
+ traits are imported. It is conventionally used when importing extension
+ traits.
+
+- Some students may be wondering: does the extension trait pattern provide
+ enough value to justify the additional boilerplate? Wouldn't a free function
+ be enough?
+
+ Show how the same example could be implemented using an `is_palindrome` free
+ function, with a single `&str` input parameter:
+
+ ```rust
+ fn is_palindrome(s: &str) -> bool {
+ s.chars().eq(s.chars().rev())
+ }
+ ```
+
+ A bespoke extension trait might be an overkill if you want to add a single
+ method to a foreign type. Both a free function and an extension trait will
+ require an additional import, and the familiarity of the method calling syntax
+ may not be enough to justify the boilerplate of a trait definition.
+
+ Nonetheless, extension methods can be **easier to discover** than free
+ functions. In particular, language servers (e.g. `rust-analyzer`) will suggest
+ extension methods if you type `.` after an instance of the foreign type.
+
+
+
+[1]: https://rust-lang.github.io/rfcs/0445-extension-trait-rfc.html
+[2]: https://github.com/rust-lang/rfcs/blob/master/text/2451-re-rebalancing-coherence.md#what-is-coherence-and-why-do-we-care
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
new file mode 100644
index 000000000000..a06ea97b07b3
--- /dev/null
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
@@ -0,0 +1,79 @@
+---
+minutes: 15
+---
+
+# Method Resolution Conflicts
+
+What happens when you have a name conflict between an inherent method and an
+extension method?
+
+```rust
+mod ext {
+ pub trait StrExt {
+ fn trim_ascii(&self) -> &str;
+ }
+
+ impl StrExt for &str {
+ fn trim_ascii(&self) -> &str {
+ self.trim_start_matches(|c: char| c.is_ascii_whitespace())
+ }
+ }
+}
+
+pub use ext::StrExt;
+// Which `trim_ascii` method is invoked?
+// The one from `StrExt`? Or the inherent one from `str`?
+assert_eq!(" dad ".trim_ascii(), "dad");
+```
+
+
+
+- The foreign type may, in a newer version, add a new inherent method with the
+ same name of our extension method.
+
+ Survey the class: what do the students think will happen in the example above?
+ Will there be a compiler error? Will one of the two methods be given higher
+ priority? Which one?
+
+ Add a `panic!("Extension trait")` in the body of `StrExt::trim_ascii` to
+ clarify which method is being invoked.
+
+- [Inherent methods have higher priority than trait methods][1], _if_ they have
+ the same name and the **same receiver**, e.g. they both expect `&self` as
+ input. The situation becomes more nuanced if the use a **different receiver**,
+ e.g. `&mut self` vs `&self`.
+
+ Change the signature of `StrExt::trim_ascii` to
+ `fn trim_ascii(&mut self) -> &str` and modify the invocation accordingly:
+
+ ```rust
+ assert_eq!((&mut " dad ").trim_ascii(), "dad");
+ ```
+
+ Now `StrExt::trim_ascii` is invoked, rather than the inherent method, since
+ `&mut self` is a more specific receiver than `&self`, the one used by the
+ inherent method.
+
+ Point the students to the Rust reference for more information on
+ [method resolution][2]. An explanation with more extensive examples can be
+ found in [an open PR to the Rust reference][3].
+
+- Avoid naming conflicts between extension trait methods and inherent methods.
+ Rust's method resolution algorithm is complex and may surprise users of your
+ code.
+
+## More to explore
+
+- The interaction between the priority search used by Rust's method resolution
+ algorithm and automatic `Deref`ering can be used to emulate
+ [specialization][4] on the stable toolchain, primarily in the context of
+ macro-generated code. Check out ["Autoref Specialization"][5] for the specific
+ details.
+
+
+
+[1]: https://doc.rust-lang.org/stable/reference/expressions/method-call-expr.html#r-expr.method.candidate-search
+[2]: https://doc.rust-lang.org/stable/reference/expressions/method-call-expr.html
+[3]: https://github.com/rust-lang/reference/pull/1725
+[4]: https://github.com/rust-lang/rust/issues/31844
+[5]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
From 408962cda55e612bc625a4c3bd74fd9c8b6f5472 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Wed, 9 Jul 2025 21:11:48 +0200
Subject: [PATCH 02/16] Reword
---
.../leveraging-the-type-system/extension-traits.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits.md
index de44b5ba1b0d..a502bd759995 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits.md
@@ -19,10 +19,10 @@ You can use the **extension trait pattern** to work around this limitation.
-- Try to compile the example to show the compiler error that's emitted.
+- Compile the example to show the compiler error that's emitted.
- Point out, in particular, how the compiler error message nudges you towards
- the extension trait pattern.
+ Highlight how the compiler error message nudges you towards the extension
+ trait pattern.
- Explain how many type-system restrictions in Rust aim to prevent _ambiguity_.
From 1670b6468c86be8bfb96b53452ed14be5457f598 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Mon, 14 Jul 2025 15:36:42 +0200
Subject: [PATCH 03/16] Use consistent terminology
---
.../extension-traits/method-resolution-conflicts.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
index a06ea97b07b3..e899b913a69c 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
@@ -51,8 +51,8 @@ assert_eq!(" dad ".trim_ascii(), "dad");
```
Now `StrExt::trim_ascii` is invoked, rather than the inherent method, since
- `&mut self` is a more specific receiver than `&self`, the one used by the
- inherent method.
+ `&mut self` has a higher priority than `&self`, the one used by the inherent
+ method.
Point the students to the Rust reference for more information on
[method resolution][2]. An explanation with more extensive examples can be
From bd1b26db22327b73f753c25985628ba3344b509d Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Mon, 14 Jul 2025 17:15:18 +0200
Subject: [PATCH 04/16] Extending other traits
---
src/SUMMARY.md | 2 +-
.../extending-foreign-traits.md | 7 --
.../extending-other-traits.md | 90 +++++++++++++++++++
3 files changed, 91 insertions(+), 8 deletions(-)
delete mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md
create mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index bc121993d98a..bedd1b5dd2e8 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -440,7 +440,7 @@
- [Extension Traits](idiomatic/leveraging-the-type-system/extension-traits.md)
- [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md)
- [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md)
- - [Extending Foreign Traits](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md)
+ - [Extending Other Traits](idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md)
---
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md
deleted file mode 100644
index a56446f4c2e4..000000000000
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-traits.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Extending Foreign Traits
-
-- TODO: Show how extension traits can be used to extend traits rather than
- types.
-- TODO: Show disambiguation syntax for naming conflicts between trait methods
- and extension trait methods.
-- https://github.com/rust-lang/rfcs/blob/master/text/0132-ufcs.md
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
new file mode 100644
index 000000000000..0a2562cefe97
--- /dev/null
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
@@ -0,0 +1,90 @@
+---
+minutes: 10
+---
+
+# Extending Other Traits
+
+Extension traits can attach new methods to _all_ implementors of a given trait:
+
+```rust
+mod ext {
+ use std::fmt::Display;
+
+ pub trait DisplayExt {
+ fn quoted(&self) -> String;
+ }
+
+ impl DisplayExt for T {
+ fn quoted(&self) -> String {
+ format!("'{}'", self)
+ }
+ }
+}
+
+pub use ext::DisplayExt as _;
+
+assert_eq!("dad".quoted(), "'dad'");
+assert_eq!(4.quoted(), "'4'");
+assert_eq!(true.quoted(), "'true'");
+```
+
+
+
+- Highlight how we added new behaviour to _multiple_ distinct types at once.
+ `.quoted()` can be called on string slices, numbers and booleans since they
+ all implement the `Display` trait.
+
+ This flavour of the extension trait pattern is built on top of
+ [_blanket implementations_][1].
+
+ Blanket implementations allow us to implement a trait for a generic type `T`,
+ as long as it satisfies the trait bounds specified in the `impl` block. In
+ this case, the only requirement is that `T` implements the `Display` trait.
+
+- Conventionally, the extension trait is named after the trait it extends,
+ following by the `Ext` suffix. In the example above, `DisplayExt`.
+
+- There are entire libraries aimed at extending foundational traits with new
+ functionality.
+
+ [`itertools`] provides a wide range of iterator adapters and utilities via the
+ [`Itertools`] trait. [`futures`] provides [`FutureExt`] to extend the
+ [`Future`] trait.
+
+## More To Explore
+
+- Extension traits can be used by libraries to distinguish between stable and
+ experimental methods.
+
+ Stable methods are part of the trait definition.
+
+ Experimental methods are provided via an extension trait defined in a
+ different library, with a less restrictive stability policy. Some utility
+ methods are then "promoted" to the core trait definition once they have been
+ proven useful and their design has been refined.
+
+- Extension traits can be used to split a [dyn-incompatible trait][2] in two:
+
+ - A **dyn-compatible core**, restricted to the methods that satisfy
+ dyn-compatibility requirements.
+ - An **extension trait**, containing the remaining methods that are not
+ dyn-compatible. (e.g., methods with a generic parameter).
+
+- Concrete types that implement the core trait will be able to invoke all
+ methods, thanks to the blanket impl for the extension trait. Trait objects
+ (`dyn CoreTrait`) will be able to invoke all methods on the core trait as well
+ as those on the extension trait that don't require `Self: Sized`.
+
+
+
+- TODO: Show disambiguation syntax for naming conflicts between trait methods
+ and extension trait methods.
+- https://github.com/rust-lang/rfcs/blob/master/text/0132-ufcs.md
+
+[1]: https://doc.rust-lang.org/stable/reference/glossary.html#blanket-implementation
+[`itertools`]: https://docs.rs/itertools/latest/itertools/
+[`Itertools`]: https://docs.rs/itertools/latest/itertools/trait.Itertools.html
+[`futures`]: https://docs.rs/futures/latest/futures/
+[`FutureExt`]: https://docs.rs/futures/latest/futures/future/trait.FutureExt.html
+[`Future`]: https://docs.rs/futures/latest/futures/future/trait.Future.html
+[2]: https://doc.rust-lang.org/reference/items/traits.html#r-items.traits.dyn-compatible
From 06f251e78c143ac30c749560781d82f900d9cb19 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Mon, 14 Jul 2025 17:25:43 +0200
Subject: [PATCH 05/16] Trait method conflicts
---
src/SUMMARY.md | 1 +
.../extending-other-traits.md | 6 +-
.../trait-method-resolution-conflicts.md | 62 +++++++++++++++++++
3 files changed, 64 insertions(+), 5 deletions(-)
create mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index bedd1b5dd2e8..3e81b0c014ed 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -441,6 +441,7 @@
- [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md)
- [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md)
- [Extending Other Traits](idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md)
+ - [Trait Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md)
---
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
index 0a2562cefe97..ecbc89a028a2 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
@@ -1,5 +1,5 @@
---
-minutes: 10
+minutes: 15
---
# Extending Other Traits
@@ -77,10 +77,6 @@ assert_eq!(true.quoted(), "'true'");
-- TODO: Show disambiguation syntax for naming conflicts between trait methods
- and extension trait methods.
-- https://github.com/rust-lang/rfcs/blob/master/text/0132-ufcs.md
-
[1]: https://doc.rust-lang.org/stable/reference/glossary.html#blanket-implementation
[`itertools`]: https://docs.rs/itertools/latest/itertools/
[`Itertools`]: https://docs.rs/itertools/latest/itertools/trait.Itertools.html
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md
new file mode 100644
index 000000000000..ac8a6a6b4c21
--- /dev/null
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md
@@ -0,0 +1,62 @@
+---
+minutes: 5
+---
+
+# Trait Method Resolution Conflicts
+
+What happens when you have a name conflict between two different trait methods
+implemented for the same type?
+
+```rust
+mod ext {
+ pub trait Ext1 {
+ fn is_palindrome(&self) -> bool;
+ }
+
+ pub trait Ext2 {
+ fn is_palindrome(&self) -> bool;
+ }
+
+ impl Ext1 for &str {
+ fn is_palindrome(&self) -> bool {
+ self.chars().eq(self.chars().rev())
+ }
+ }
+
+ impl Ext2 for &str {
+ fn is_palindrome(&self) -> bool {
+ self.chars().eq(self.chars().rev())
+ }
+ }
+}
+
+pub use ext::Ext1;
+pub use ext::Ext2;
+
+// Which method is invoked?
+// The one from `Ext1`? Or the one from `Ext2`?
+assert!("dad".is_palindrome());
+```
+
+
+
+- The extended trait may, in a newer version, add a new trait method with the
+ same name of our extension method.
+
+ Survey the class: what do the students think will happen in the example above?
+ Will there be a compiler error? Will one of the two methods be given higher
+ priority? Which one?
+
+- The compiler rejects the code because it cannot determine which method to
+ invoke. Neither `Ext1` nor `Ext2` has a higher priority than the other.
+
+ To resolve this conflict, you must specify which trait you want to use. For
+ example, you can call `Ext1::is_palindrome("dad")` or
+ `Ext2::is_palindrome("dad")`.
+
+ For methods with more complex signatures, you may need to use a more explicit
+ [fully-qualified syntax][1].
+
+
+
+[1]: https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls
From 21a105e147cb69851a15bf7754c5616870050dd8 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Mon, 14 Jul 2025 17:36:16 +0200
Subject: [PATCH 06/16] Shorter title
---
src/SUMMARY.md | 2 +-
...method-resolution-conflicts.md => trait-method-conflicts.md} | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
rename src/idiomatic/leveraging-the-type-system/extension-traits/{trait-method-resolution-conflicts.md => trait-method-conflicts.md} (97%)
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 3e81b0c014ed..fd6ab1659d7f 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -441,7 +441,7 @@
- [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md)
- [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md)
- [Extending Other Traits](idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md)
- - [Trait Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md)
+ - [Trait Method Conflicts](idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md)
---
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
similarity index 97%
rename from src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md
rename to src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
index ac8a6a6b4c21..95415b905294 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-resolution-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
@@ -2,7 +2,7 @@
minutes: 5
---
-# Trait Method Resolution Conflicts
+# Trait Method Conflicts
What happens when you have a name conflict between two different trait methods
implemented for the same type?
From b64886c7f9c4e57a5c54218460f37569e4f8547b Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Mon, 14 Jul 2025 17:45:18 +0200
Subject: [PATCH 07/16] Mark trait method conflict as compile_fail
---
.../extension-traits/trait-method-conflicts.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
index 95415b905294..91817a8e9e9b 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
@@ -7,7 +7,7 @@ minutes: 5
What happens when you have a name conflict between two different trait methods
implemented for the same type?
-```rust
+```rust,compile_fail
mod ext {
pub trait Ext1 {
fn is_palindrome(&self) -> bool;
From c36f7fe24d6b4758d259700d554ea8ce27d1b009 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Tue, 15 Jul 2025 11:36:59 +0200
Subject: [PATCH 08/16] Fix link
---
.../extension-traits/extending-foreign-types.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
index 99a9f71d9342..187274be92f4 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
@@ -76,5 +76,5 @@ assert!(!"grandma".is_palindrome());
-[1]: https://rust-lang.github.io/rfcs/0445-extension-trait-rfc.html
+[1]: https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html
[2]: https://github.com/rust-lang/rfcs/blob/master/text/2451-re-rebalancing-coherence.md#what-is-coherence-and-why-do-we-care
From 5ab37d8272329f7114f576c4ecee79a7dcb0c226 Mon Sep 17 00:00:00 2001
From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>
Date: Tue, 15 Jul 2025 14:38:57 +0200
Subject: [PATCH 09/16] Update
src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
Co-authored-by: Dmitri Gribenko
---
.../extension-traits/extending-foreign-types.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
index 187274be92f4..5a3641467cb0 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
@@ -20,9 +20,9 @@ mod ext {
}
}
-// Bring the extension trait into scope..
+// Bring the extension trait into scope...
pub use ext::StrExt as _;
-// ..then invoke its methods as if they were inherent methods
+// ...then invoke its methods as if they were inherent methods
assert!("dad".is_palindrome());
assert!(!"grandma".is_palindrome());
```
From 841bce5f329a4c564757e992e38a3fe82ef7de97 Mon Sep 17 00:00:00 2001
From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>
Date: Tue, 15 Jul 2025 14:47:36 +0200
Subject: [PATCH 10/16] Update
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Co-authored-by: Dmitri Gribenko
---
.../extension-traits/method-resolution-conflicts.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
index e899b913a69c..b19c520b9ddb 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
@@ -41,7 +41,7 @@ assert_eq!(" dad ".trim_ascii(), "dad");
- [Inherent methods have higher priority than trait methods][1], _if_ they have
the same name and the **same receiver**, e.g. they both expect `&self` as
input. The situation becomes more nuanced if the use a **different receiver**,
- e.g. `&mut self` vs `&self`.
+ e.g., `&mut self` vs `&self`.
Change the signature of `StrExt::trim_ascii` to
`fn trim_ascii(&mut self) -> &str` and modify the invocation accordingly:
From aa2ab0fd705b1fbc72746c9283f3391df14e7cbc Mon Sep 17 00:00:00 2001
From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>
Date: Tue, 15 Jul 2025 14:47:49 +0200
Subject: [PATCH 11/16] Update
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Co-authored-by: Dmitri Gribenko
---
.../extension-traits/method-resolution-conflicts.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
index b19c520b9ddb..c6af0ebfb556 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
@@ -65,7 +65,7 @@ assert_eq!(" dad ".trim_ascii(), "dad");
## More to explore
- The interaction between the priority search used by Rust's method resolution
- algorithm and automatic `Deref`ering can be used to emulate
+ algorithm and automatic `Deref`ing can be used to emulate
[specialization][4] on the stable toolchain, primarily in the context of
macro-generated code. Check out ["Autoref Specialization"][5] for the specific
details.
From 63d3aa33fdb94d6f8f18c9cde72c4c311b2f607d Mon Sep 17 00:00:00 2001
From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com>
Date: Tue, 15 Jul 2025 14:48:07 +0200
Subject: [PATCH 12/16] Update
src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
Co-authored-by: Dmitri Gribenko
---
.../extension-traits/method-resolution-conflicts.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
index c6af0ebfb556..68378915636a 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
@@ -39,7 +39,7 @@ assert_eq!(" dad ".trim_ascii(), "dad");
clarify which method is being invoked.
- [Inherent methods have higher priority than trait methods][1], _if_ they have
- the same name and the **same receiver**, e.g. they both expect `&self` as
+ the same name and the **same receiver**, e.g., they both expect `&self` as
input. The situation becomes more nuanced if the use a **different receiver**,
e.g., `&mut self` vs `&self`.
From 17ba065295a8e0007a29ac1f41a599bc8eed7e65 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Thu, 31 Jul 2025 16:52:18 +0200
Subject: [PATCH 13/16] Address review comments
---
.../extension-traits.md | 44 +++++++++++++++----
.../extending-foreign-types.md | 14 ++++--
.../trait-method-conflicts.md | 6 ++-
3 files changed, 50 insertions(+), 14 deletions(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits.md
index a502bd759995..1e8380f09836 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits.md
@@ -1,10 +1,14 @@
---
-minutes: 5
+minutes: 15
---
# Extension Traits
-In Rust, you can't define new inherent methods for foreign types.
+It may desirable to **extend** foreign types with new inherent methods. For
+example, allow your code to check if a string is a palindrome using
+method-calling syntax: `s.is_palindrome()`.
+
+It might feel natural to reach out for an `impl` block:
```rust,compile_fail
// 🛠️❌
@@ -15,10 +19,22 @@ impl &'_ str {
}
```
-You can use the **extension trait pattern** to work around this limitation.
+The Rust compiler won't allow it, though. But you can use the **extension trait
+pattern** to work around this limitation.
+- Start by explaining the terminology.
+
+ A Rust item (be it a trait or a type) is referred to as:
+
+ - **foreign**, if it isn't defined in the current crate
+ - **local**, if it is defined in the current crate
+
+ The distinction has significant implications for
+ [coherence and orphan rules][1], as we'll get a chance to explore in this
+ section of the course.
+
- Compile the example to show the compiler error that's emitted.
Highlight how the compiler error message nudges you towards the extension
@@ -26,12 +42,22 @@ You can use the **extension trait pattern** to work around this limitation.
- Explain how many type-system restrictions in Rust aim to prevent _ambiguity_.
- If you were allowed to define new inherent methods on foreign types, there
- would need to be a mechanism to disambiguate between distinct inherent methods
- with the same name.
+ What would happen if you were allowed to define new inherent methods on
+ foreign types? Different crates in your dependency tree might end up defining
+ different methods on the same foreign type with the same name.
+
+ As soon as there is room for ambiguity, there must be a way to disambiguate.
+ If disambiguation happens implicitly, it can lead to suprising or otherwise
+ unexpected behavior. If disambiguation happens explicitly, it can increase the
+ cognitive load on developers who are reading your code.
- In particular, adding a new inherent method to a library type could cause
- errors in downstream code if the name of the new method conflicts with an
- inherent method that's been defined in the consuming crate.
+ Furthermore, every time a crate defines a new inherent method on a foreign
+ type, it may cause compilation errors in _your_ code, as you may be forced to
+ introduce explicit disambiguation.
+
+ Rust has decided to avoid the issue altogether by forbidding the definition of
+ new inherent methods on foreign types.
+
+[1]: https://doc.rust-lang.org/stable/reference/items/implementations.html#r-items.impl.trait.orphan-rule
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
index 5a3641467cb0..83a30d32c149 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
@@ -48,9 +48,16 @@ assert!(!"grandma".is_palindrome());
that's emitted if you try to invoke an extension method without having the
corresponding extension trait in scope.
-- The `as _` syntax reduces the likelihood of naming conflicts when multiple
- traits are imported. It is conventionally used when importing extension
- traits.
+- The example above uses an [_underscore import_][3] (`use ext::StrExt as _`) to
+ minimize the likelihood of a naming conflict with other imported traits.
+
+ With an underscore import, the trait is considered to be in scope and you're
+ allowed to invoke its methods on types that implement the trait. Its _symbol_,
+ instead, is not directly accessible. This prevents you, for example, from
+ using that trait in a `where` clause.
+
+ Since extension traits aren't meant to be used in `where` clauses, they are
+ conventionally imported via an underscore import.
- Some students may be wondering: does the extension trait pattern provide
enough value to justify the additional boilerplate? Wouldn't a free function
@@ -78,3 +85,4 @@ assert!(!"grandma".is_palindrome());
[1]: https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html
[2]: https://github.com/rust-lang/rfcs/blob/master/text/2451-re-rebalancing-coherence.md#what-is-coherence-and-why-do-we-care
+[3]: https://doc.rust-lang.org/stable/reference/items/use-declarations.html#r-items.use.as-underscore
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
index 91817a8e9e9b..6379b03a8dbc 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md
@@ -40,8 +40,10 @@ assert!("dad".is_palindrome());
-- The extended trait may, in a newer version, add a new trait method with the
- same name of our extension method.
+- The trait you are extending may, in a newer version, add a new trait method
+ with the same name of your extension method. Or another extension trait for
+ the same type may define a method with a name that conflicts with your own
+ extension method.
Survey the class: what do the students think will happen in the example above?
Will there be a compiler error? Will one of the two methods be given higher
From d10c9859fca1435d9b286929919925be3f162470 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Thu, 31 Jul 2025 16:55:45 +0200
Subject: [PATCH 14/16] Formatting and typos
---
.../leveraging-the-type-system/extension-traits.md | 2 +-
.../extension-traits/method-resolution-conflicts.md | 7 +++----
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits.md
index 1e8380f09836..f5bc3025b189 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits.md
@@ -47,7 +47,7 @@ pattern** to work around this limitation.
different methods on the same foreign type with the same name.
As soon as there is room for ambiguity, there must be a way to disambiguate.
- If disambiguation happens implicitly, it can lead to suprising or otherwise
+ If disambiguation happens implicitly, it can lead to surprising or otherwise
unexpected behavior. If disambiguation happens explicitly, it can increase the
cognitive load on developers who are reading your code.
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
index 68378915636a..df91c0e58360 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md
@@ -65,10 +65,9 @@ assert_eq!(" dad ".trim_ascii(), "dad");
## More to explore
- The interaction between the priority search used by Rust's method resolution
- algorithm and automatic `Deref`ing can be used to emulate
- [specialization][4] on the stable toolchain, primarily in the context of
- macro-generated code. Check out ["Autoref Specialization"][5] for the specific
- details.
+ algorithm and automatic `Deref`ing can be used to emulate [specialization][4]
+ on the stable toolchain, primarily in the context of macro-generated code.
+ Check out ["Autoref Specialization"][5] for the specific details.
From 771a37a8c9c561a4d149f52d4a9ac8bcc39332ca Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Thu, 31 Jul 2025 17:09:40 +0200
Subject: [PATCH 15/16] Elaborate further on the desired goal when extending
other traits
---
.../extension-traits/extending-other-traits.md | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
index ecbc89a028a2..d8d4e8ba34fd 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md
@@ -4,7 +4,8 @@ minutes: 15
# Extending Other Traits
-Extension traits can attach new methods to _all_ implementors of a given trait:
+As with types, it may be desirable to **extend foreign traits**. In particular,
+to attach new methods to _all_ implementors of a given trait.
```rust
mod ext {
@@ -41,6 +42,14 @@ assert_eq!(true.quoted(), "'true'");
as long as it satisfies the trait bounds specified in the `impl` block. In
this case, the only requirement is that `T` implements the `Display` trait.
+- Draw the students attention to the implementation of `DisplayExt::quoted`: we
+ can't make any assumptions about the type of `T` other than that it implements
+ `Display`. All our logic must either use methods from `Display` or
+ functions/macros that doesn't require `T` to implement any other trait.
+
+ We could introduce additional trait bounds on `T`, but it would restrict the
+ set of types that can leverage the extension trait.
+
- Conventionally, the extension trait is named after the trait it extends,
following by the `Ext` suffix. In the example above, `DisplayExt`.
From b58a2a52135dcb259e8a433361483ec387565723 Mon Sep 17 00:00:00 2001
From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Date: Thu, 31 Jul 2025 17:16:52 +0200
Subject: [PATCH 16/16] Extract bullet point into its own slide
---
src/SUMMARY.md | 1 +
.../extending-foreign-types.md | 24 +----------
.../should-i-define-an-extension-trait.md | 40 +++++++++++++++++++
3 files changed, 42 insertions(+), 23 deletions(-)
create mode 100644 src/idiomatic/leveraging-the-type-system/extension-traits/should-i-define-an-extension-trait.md
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index fd6ab1659d7f..6c22e06eff86 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -440,6 +440,7 @@
- [Extension Traits](idiomatic/leveraging-the-type-system/extension-traits.md)
- [Extending Foreign Types](idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md)
- [Method Resolution Conflicts](idiomatic/leveraging-the-type-system/extension-traits/method-resolution-conflicts.md)
+ - [Should I Define An Extension Trait?](idiomatic/leveraging-the-type-system/extension-traits/should-i-define-an-extension-trait.md)
- [Extending Other Traits](idiomatic/leveraging-the-type-system/extension-traits/extending-other-traits.md)
- [Trait Method Conflicts](idiomatic/leveraging-the-type-system/extension-traits/trait-method-conflicts.md)
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
index 83a30d32c149..bf781700f09a 100644
--- a/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/extending-foreign-types.md
@@ -1,5 +1,5 @@
---
-minutes: 15
+minutes: 10
---
# Extending Foreign Types
@@ -59,28 +59,6 @@ assert!(!"grandma".is_palindrome());
Since extension traits aren't meant to be used in `where` clauses, they are
conventionally imported via an underscore import.
-- Some students may be wondering: does the extension trait pattern provide
- enough value to justify the additional boilerplate? Wouldn't a free function
- be enough?
-
- Show how the same example could be implemented using an `is_palindrome` free
- function, with a single `&str` input parameter:
-
- ```rust
- fn is_palindrome(s: &str) -> bool {
- s.chars().eq(s.chars().rev())
- }
- ```
-
- A bespoke extension trait might be an overkill if you want to add a single
- method to a foreign type. Both a free function and an extension trait will
- require an additional import, and the familiarity of the method calling syntax
- may not be enough to justify the boilerplate of a trait definition.
-
- Nonetheless, extension methods can be **easier to discover** than free
- functions. In particular, language servers (e.g. `rust-analyzer`) will suggest
- extension methods if you type `.` after an instance of the foreign type.
-
[1]: https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html
diff --git a/src/idiomatic/leveraging-the-type-system/extension-traits/should-i-define-an-extension-trait.md b/src/idiomatic/leveraging-the-type-system/extension-traits/should-i-define-an-extension-trait.md
new file mode 100644
index 000000000000..e8d367ec1221
--- /dev/null
+++ b/src/idiomatic/leveraging-the-type-system/extension-traits/should-i-define-an-extension-trait.md
@@ -0,0 +1,40 @@
+---
+minutes: 5
+---
+
+# Should I Define An Extension Trait?
+
+In what scenarios should you prefer an extension trait over a free function?
+
+```rust
+pub trait StrExt {
+ fn is_palindrome(&self) -> bool;
+}
+
+impl StrExt for &str {
+ fn is_palindrome(&self) -> bool {
+ self.chars().eq(self.chars().rev())
+ }
+}
+
+// vs
+
+fn is_palindrome(s: &str) -> bool {
+ s.chars().eq(s.chars().rev())
+}
+```
+
+The main advantage of extension traits is **ease of discovery**.
+
+
+
+- A bespoke extension trait might be an overkill if you want to add a single
+ method to a foreign type. Both a free function and an extension trait will
+ require an additional import, and the familiarity of the method calling syntax
+ may not be enough to justify the boilerplate of a trait definition.
+
+ Nonetheless, extension methods can be **easier to discover** than free
+ functions. In particular, language servers (e.g. `rust-analyzer`) will suggest
+ extension methods if you type `.` after an instance of the foreign type.
+
+