From c9ec8bb06fbd83399934110007fef4f71b4b307f Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 04:56:53 -0300 Subject: [PATCH 1/9] docs: rename "object safety" to "dyn compatibility" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rust renamed the concept in 1.84. The Reference, rustdoc and the compiler all use "dyn compatibility" now — E0038 reads "the trait `X` is not dyn compatible". Pure terminology change, no technical content touched. Also fixes an internal inconsistency in async-book ch10, where line 80 already said "not dyn-compatible" while line 143 still said "not object-safe". Ref: https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility Co-Authored-By: Claude Opus 5 --- async-book/src/ch10-async-traits.md | 2 +- rust-patterns-book/src/ch02-traits-in-depth.md | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/async-book/src/ch10-async-traits.md b/async-book/src/ch10-async-traits.md index a284e61..e2a6988 100644 --- a/async-book/src/ch10-async-traits.md +++ b/async-book/src/ch10-async-traits.md @@ -140,7 +140,7 @@ async fn spawn_lookup(store: Arc) { // ⚠️ Note: trait_variant does NOT enable dyn dispatch. // The generated trait still uses `impl Future`, so `dyn SendDataStore` -// is not object-safe. For dyn dispatch, you still need manual boxing +// is not dyn compatible. For dyn dispatch, you still need manual boxing // (see the Box::pin approach above) or the `async-trait` crate. ``` diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index 0731871..926def4 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -2,7 +2,7 @@ > **What you'll learn:** > - Associated types vs generic parameters — and when to use each -> - GATs, blanket impls, marker traits, and trait object safety rules +> - GATs, blanket impls, marker traits, and dyn compatibility rules > - How vtables and fat pointers work under the hood > - Extension traits, enum dispatch, and typed command patterns @@ -264,9 +264,9 @@ fn record_measurement(sensor: &S) { This connects directly to the **type-state pattern** in Chapter 3. -### Trait Object Safety Rules +### Dyn Compatibility (formerly "Object Safety") -Not every trait can be used as `dyn Trait`. A trait is **object-safe** only if: +Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only if: 1. **No `Self: Sized` bound** on the trait itself 2. **No generic type parameters** on methods @@ -274,7 +274,7 @@ Not every trait can be used as `dyn Trait`. A trait is **object-safe** only if: 4. **No associated functions** (methods must have `&self`, `&mut self`, or `self`) ```rust -// ✅ Object-safe — can be used as dyn Drawable +// ✅ Dyn compatible — can be used as dyn Drawable trait Drawable { fn draw(&self); fn bounding_box(&self) -> (f64, f64, f64, f64); @@ -282,20 +282,20 @@ trait Drawable { let shapes: Vec> = vec![/* ... */]; // ✅ Works -// ❌ NOT object-safe — uses Self in return position +// ❌ NOT dyn compatible — uses Self in return position trait Cloneable { fn clone_self(&self) -> Self; // ^^^^ Can't know the concrete size at runtime } // let items: Vec> = ...; // ❌ Compile error -// ❌ NOT object-safe — generic method +// ❌ NOT dyn compatible — generic method trait Converter { fn convert(&self) -> T; // ^^^ The vtable can't contain infinite monomorphizations } -// ❌ NOT object-safe — associated function (no self) +// ❌ NOT dyn compatible — associated function (no self) trait Factory { fn create() -> Self; // No &self — how would you call this through a trait object? @@ -546,7 +546,7 @@ Do you know the concrete type at compile time? | Performance | Best — inlinable | One indirection per call | | Heterogeneous collections | ❌ | ✅ | | Binary size per type | One copy each | Shared code | -| Trait must be object-safe? | No | Yes | +| Trait must be dyn compatible? | No | Yes | | Works in trait definitions | ✅ (Rust 1.75+) | Always | *** @@ -917,7 +917,7 @@ Is the set of types closed (known at compile time)? | Cache-friendly | No (pointer chasing) | Yes (contiguous) | | Open to new types | ✅ (anyone can impl) | ❌ (closed set) | | Code size | Shared | One copy per variant | -| Trait must be object-safe | Yes | No | +| Trait must be dyn compatible | Yes | No | | Adding a variant | No code changes | Update enum + match arms | ### When to Use Enum Dispatch From 95282d150e15d9fc7f38e447f32a9fcd8207a53d Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 04:58:27 -0300 Subject: [PATCH 2/9] fix(rust-patterns-book): correct the Self-in-method rule for dyn compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rule said "No use of `Self` in return position (except via indirection like `Box`)". Both halves were wrong: 1. The restriction is not limited to return position. The Reference says a dispatchable method must "not use `Self` except in the type of the receiver" — argument position counts too. That's why `PartialEq` (`fn eq(&self, other: &Self)`) is not dyn compatible, which the old wording could not explain. 2. `Box` is not an escape hatch in return position. `Box` is a valid *receiver* (`self: Box`); as a return type it still names `Self` and is rejected. The real idiom is `-> Box`, which names a concrete type rather than `Self`. Verified against rustc 1.95.0; the compiler emits two distinct messages, "references the `Self` type in its return type" and "references the `Self` type in this parameter", both quoted in the examples. Co-Authored-By: Claude Opus 5 --- .../src/ch02-traits-in-depth.md | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index 926def4..6b3cd98 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -270,7 +270,8 @@ Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only i 1. **No `Self: Sized` bound** on the trait itself 2. **No generic type parameters** on methods -3. **No use of `Self` in return position** (except via indirection like `Box`) +3. **No use of `Self`** anywhere in a method signature except in the type of the + receiver — this covers parameters as well as return types 4. **No associated functions** (methods must have `&self`, `&mut self`, or `self`) ```rust @@ -282,13 +283,31 @@ trait Drawable { let shapes: Vec> = vec![/* ... */]; // ✅ Works -// ❌ NOT dyn compatible — uses Self in return position +// ❌ NOT dyn compatible — mentions Self outside the receiver trait Cloneable { fn clone_self(&self) -> Self; - // ^^^^ Can't know the concrete size at runtime + // ^^^^ "...because method `clone_self` references + // the `Self` type in its return type" } // let items: Vec> = ...; // ❌ Compile error +// ❌ Same rule, argument position — this is why PartialEq isn't dyn compatible +trait Comparable { + fn equals(&self, other: &Self) -> bool; + // ^^^^ "...references the `Self` type in this parameter" +} + +// ⚠️ Wrapping in Box does NOT help — Box still names Self +trait Spawner { + fn spawn(&self) -> Box; // ❌ Still not dyn compatible +} + +// ✅ Return Box instead — that's a concrete type, not Self. +// This is the standard "clone through a trait object" idiom: +trait CloneableDyn { + fn clone_box(&self) -> Box; +} + // ❌ NOT dyn compatible — generic method trait Converter { fn convert(&self) -> T; From 43742d613457a5959d1923b634c2a920d0d2120f Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 04:59:10 -0300 Subject: [PATCH 3/9] fix(rust-patterns-book): correct the associated-function rule and list valid receivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rule said "No associated functions (methods must have `&self`, `&mut self`, or `self`)". Wrong on both counts: 1. Associated functions with no receiver ARE allowed — they just have to be opted out of the vtable with `where Self: Sized`. rustc itself suggests this fix in the E0038 note. 2. `self` by value is not a dispatchable receiver: it implies `where Self: Sized`. A trait with a `self`-by-value method is still dyn compatible; the method is merely absent from the vtable. Calling it through the trait object fails with E0161 ("cannot move a value of type `dyn Consume`"), not E0038 — a different failure mode worth showing. Replaces the wrong list with the actual set of dispatchable receivers: `&self`, `&mut self`, `self: Box`, `self: Rc`, `self: Arc`, `self: Pin

`. All six verified dispatching through a trait object on rustc 1.95.0. Co-Authored-By: Claude Opus 5 --- .../src/ch02-traits-in-depth.md | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index 6b3cd98..d768215 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -272,7 +272,11 @@ Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only i 2. **No generic type parameters** on methods 3. **No use of `Self`** anywhere in a method signature except in the type of the receiver — this covers parameters as well as return types -4. **No associated functions** (methods must have `&self`, `&mut self`, or `self`) +4. **Every associated function must be dispatchable or opted out.** A dispatchable + method needs a receiver of type `&self`, `&mut self`, `self: Box`, + `self: Rc`, `self: Arc`, or `self: Pin

` where `P` is one of + those. Anything else — including a bare `fn create() -> Self` — must carry + `where Self: Sized` to be excluded from the vtable ```rust // ✅ Dyn compatible — can be used as dyn Drawable @@ -314,11 +318,28 @@ trait Converter { // ^^^ The vtable can't contain infinite monomorphizations } -// ❌ NOT dyn compatible — associated function (no self) +// ❌ NOT dyn compatible — associated function with no receiver trait Factory { fn create() -> Self; - // No &self — how would you call this through a trait object? + // "...because associated function `create` has no `self` parameter" } + +// ✅ Same function, opted out of the vtable — the trait is dyn compatible again +trait FactoryFixed { + fn describe(&self) -> String; // dispatchable + fn create() -> Self where Self: Sized; // excluded from the vtable +} + +// ⚠️ `self` by value is NOT a dispatchable receiver — it implies +// `where Self: Sized`. The trait stays dyn compatible, but the method +// simply isn't in the vtable and can't be called through `dyn Trait`: +trait Consume { + fn describe(&self) -> String; // in the vtable + fn consume(self) -> String; // implicitly `where Self: Sized` +} +// let t: &dyn Consume = &token; // ✅ the trait object is fine +// boxed.consume(); // ❌ error[E0161]: cannot move a value +// // of type `dyn Consume` ``` **Workarounds**: From a6ce79dbfda80f860f0fe48da2252a1b1153b521 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 04:59:22 -0300 Subject: [PATCH 4/9] fix(rust-patterns-book): fix contradictory Sized advice in the dyn compatibility tip The rule of thumb said "no `Sized` bounds", three lines after the "Workarounds" block that recommends exactly `where Self: Sized`. The two are different things: `Sized` as a supertrait kills dyn compatibility outright (E0038, "...because it requires `Self: Sized`"), whereas `where Self: Sized` on one method removes just that method from the vtable and keeps the trait usable. Both verified on rustc 1.95.0. Co-Authored-By: Claude Opus 5 --- rust-patterns-book/src/ch02-traits-in-depth.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index d768215..6503a90 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -358,9 +358,12 @@ trait MyTrait { // when the concrete type is known. ``` -> **Rule of thumb**: If you plan to use `dyn Trait`, keep methods simple — -> no generics, no `Self` in return types, no `Sized` bounds. When in doubt, -> try `let _: Box;` and let the compiler tell you. +> **Rule of thumb**: If you plan to use `dyn Trait`, keep methods simple — no +> generic type parameters, no `Self` outside the receiver, and no `Sized` +> **supertrait**. Note the asymmetry: `trait Widget: Sized` is fatal, while +> `where Self: Sized` on an individual method is the sanctioned opt-out shown +> above. When in doubt, try `let _: Box;` and let the compiler +> tell you. ### Trait Objects Under the Hood — vtables and Fat Pointers From d404af2ed649e7865c080f1cc66a1ab0b172d556 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 04:59:36 -0300 Subject: [PATCH 5/9] docs(rust-patterns-book): note that lifetime parameters are allowed on dispatchable methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rule 2 said "No generic type parameters on methods", which is correct but incomplete. The Reference is explicit: a dispatchable method must "not have any type parameters (although lifetime parameters are allowed)". Readers routinely assume `fn first_token<'a>(&self, s: &'a str) -> &'a str` breaks dyn compatibility because it has angle brackets. It doesn't — lifetimes are erased before codegen, so one vtable slot still suffices. Verified on rustc 1.95.0, including a higher-ranked closure argument. Co-Authored-By: Claude Opus 5 --- rust-patterns-book/src/ch02-traits-in-depth.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index 6503a90..31c5ec9 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -269,7 +269,8 @@ This connects directly to the **type-state pattern** in Chapter 3. Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only if: 1. **No `Self: Sized` bound** on the trait itself -2. **No generic type parameters** on methods +2. **No generic type parameters** on methods — lifetime parameters *are* allowed, + since they're erased before codegen and need no extra vtable slot 3. **No use of `Self`** anywhere in a method signature except in the type of the receiver — this covers parameters as well as return types 4. **Every associated function must be dispatchable or opted out.** A dispatchable @@ -318,6 +319,12 @@ trait Converter { // ^^^ The vtable can't contain infinite monomorphizations } +// ✅ Dyn compatible — a generic LIFETIME is fine, only type params are barred +trait Tokenizer { + fn first_token<'a>(&self, input: &'a str) -> &'a str; + // ^^^^ One vtable slot is enough: lifetimes are erased +} + // ❌ NOT dyn compatible — associated function with no receiver trait Factory { fn create() -> Self; From ce8f4c1b60922e28fc073aa7ca659fa84ff7ee79 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 04:59:53 -0300 Subject: [PATCH 6/9] docs(rust-patterns-book): add the supertrait rules for dyn compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Reference rules were missing or loosely worded: - "All supertraits must also be dyn compatible." Added as rule 5. It's worth its own entry because the failure is non-local: your trait looks fine and the compiler blames a method in somebody else's trait. - Rule 1 restated as "`Sized` must not be a supertrait", matching the Reference, instead of "No `Self: Sized` bound on the trait itself" — which reads as if it also covered per-method `where Self: Sized` bounds. Verified on rustc 1.95.0: `trait Derived: Base` where `Base` has a receiverless associated function yields E0038 pointing at `Base::make`. Co-Authored-By: Claude Opus 5 --- rust-patterns-book/src/ch02-traits-in-depth.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index 31c5ec9..a2be4ca 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -268,7 +268,7 @@ This connects directly to the **type-state pattern** in Chapter 3. Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only if: -1. **No `Self: Sized` bound** on the trait itself +1. **`Sized` must not be a supertrait** — i.e. the trait must not require `Self: Sized` 2. **No generic type parameters** on methods — lifetime parameters *are* allowed, since they're erased before codegen and need no extra vtable slot 3. **No use of `Self`** anywhere in a method signature except in the type of the @@ -278,6 +278,7 @@ Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only i `self: Rc`, `self: Arc`, or `self: Pin

` where `P` is one of those. Anything else — including a bare `fn create() -> Self` — must carry `where Self: Sized` to be excluded from the vtable +5. **All supertraits must themselves be dyn compatible** — the property is inherited ```rust // ✅ Dyn compatible — can be used as dyn Drawable @@ -337,6 +338,16 @@ trait FactoryFixed { fn create() -> Self where Self: Sized; // excluded from the vtable } +// ❌ NOT dyn compatible — inherited from a supertrait that isn't. +// Nothing is wrong with Derived itself; the compiler points at Base::make. +trait Base { + fn make() -> Self; +} +trait Derived: Base { + fn show(&self); +} +// let d: &dyn Derived = ...; // ❌ Compile error, blamed on `Base::make` + // ⚠️ `self` by value is NOT a dispatchable receiver — it implies // `where Self: Sized`. The trait stays dyn compatible, but the method // simply isn't in the vtable and can't be called through `dyn Trait`: From 5cd657fd44b926ed93a2fc784228d2c7fa48e9a3 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 05:00:09 -0300 Subject: [PATCH 7/9] docs(rust-patterns-book): add associated const and GAT restrictions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two more Reference rules the chapter didn't list: a dyn compatible trait "must not have any associated constants" and "must not have any associated types with generics". The GAT one matters here in particular — the chapter teaches GATs a few sections earlier with a `LendingIterator` example, without mentioning that such a trait can never become `dyn LendingIterator`. Cross-referenced. Also records something the Reference leaves implicit and that readers will try by analogy with methods: there is no `where Self: Sized` opt-out for an associated constant. `const MAX: f64 where Self: Sized;` doesn't compile at all on stable — E0658, generic const items are experimental (rust#113521). The workaround is to turn the constant into a method. Plus the E0191 note: a plain associated type keeps the trait dyn compatible but must be specified in the trait object type. All verified on rustc 1.95.0. Co-Authored-By: Claude Opus 5 --- .../src/ch02-traits-in-depth.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index a2be4ca..16845b8 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -279,6 +279,9 @@ Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only i those. Anything else — including a bare `fn create() -> Self` — must carry `where Self: Sized` to be excluded from the vtable 5. **All supertraits must themselves be dyn compatible** — the property is inherited +6. **No associated constants**, and **no associated types with generics** (GATs). + Plain associated types are fine, but must be pinned down at the use site: + `dyn Iterator`, never a bare `dyn Iterator` ```rust // ✅ Dyn compatible — can be used as dyn Drawable @@ -348,6 +351,25 @@ trait Derived: Base { } // let d: &dyn Derived = ...; // ❌ Compile error, blamed on `Base::make` +// ❌ NOT dyn compatible — an associated const has no vtable representation +trait Sensor { + const MAX: f64; + // ^^^ "...because it contains associated const `MAX`" + fn read(&self) -> f64; +} +// Workaround: make it a method — `fn max_reading(&self) -> f64`. Note there is +// NO `where Self: Sized` escape hatch here; `const MAX: f64 where Self: Sized;` +// isn't even accepted syntax on stable (generic const items are unstable). + +// ❌ NOT dyn compatible — a GAT is a family of types, not one type +trait LendingIterator { + type Item<'a> where Self: 'a; + // ^^^^ "...because it contains generic associated type `Item`" + fn next(&mut self) -> Option>; +} +// This is the same LendingIterator from the GATs section above: the price of +// a lending iterator is that `dyn LendingIterator` can never exist. + // ⚠️ `self` by value is NOT a dispatchable receiver — it implies // `where Self: Sized`. The trait stays dyn compatible, but the method // simply isn't in the vtable and can't be called through `dyn Trait`: From 57ebf2845855111b7c18e1337c341e032a72c49f Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 05:00:32 -0300 Subject: [PATCH 8/9] docs(rust-patterns-book): add the opaque return type restriction (async fn / impl Trait) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last of the missing Reference rules: a dispatchable method must "not have an opaque return type", covering both `async fn` and return-position `impl Trait`. This one is load-bearing for this chapter specifically. The RPITIT section below presents `fn items(&self) -> impl Iterator` as a strict improvement over `Box` ("RPITIT removes the allocation") without mentioning that it also removes the option of `dyn Container`. Added a cross-reference in both directions, plus one to async-book ch10, which already covers the `Pin>` workaround. Also folds in the `AsyncFn`/`AsyncFnMut`/`AsyncFnOnce` rule, but explains it rather than restating it: rustc blames `AsyncFnMut::CallRefFuture<'a>`, so it's a corollary of the GAT rule, not an independent special case. The contrast with plain `Fn` (dyn compatible) makes the point concrete. Verified on rustc 1.95.0 — both still rejected, and both workarounds exercised through a real trait object. Co-Authored-By: Claude Opus 5 --- .../src/ch02-traits-in-depth.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index 16845b8..af2e984 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -282,6 +282,8 @@ Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only i 6. **No associated constants**, and **no associated types with generics** (GATs). Plain associated types are fine, but must be pinned down at the use site: `dyn Iterator`, never a bare `dyn Iterator` +7. **No opaque return types** on dispatchable methods — neither `async fn` (which + hides a `Future` type) nor return-position `impl Trait` ```rust // ✅ Dyn compatible — can be used as dyn Drawable @@ -370,6 +372,22 @@ trait LendingIterator { // This is the same LendingIterator from the GATs section above: the price of // a lending iterator is that `dyn LendingIterator` can never exist. +// ❌ NOT dyn compatible — opaque return type (RPITIT) +trait Container { + fn items(&self) -> impl Iterator; + // ^^^^ "...references an `impl Trait` type in its return type" +} + +// ❌ NOT dyn compatible — `async fn` is the same problem with sugar on top +trait DataStore { + async fn get(&self, key: &str) -> Option; + // "...because method `get` is `async`" +} +// Workaround for both: erase the type yourself and return a concrete boxed +// trait object — `Box + '_>` and +// `Pin> + '_>>` are ordinary types, +// so they get ordinary vtable slots. + // ⚠️ `self` by value is NOT a dispatchable receiver — it implies // `where Self: Sized`. The trait stays dyn compatible, but the method // simply isn't in the vtable and can't be called through `dyn Trait`: @@ -405,6 +423,18 @@ trait MyTrait { > above. When in doubt, try `let _: Box;` and let the compiler > tell you. +> **Why `AsyncFn` isn't dyn compatible**: the Reference lists `AsyncFn`, +> `AsyncFnMut` and `AsyncFnOnce` as a separate rule, but the compiler shows it's +> really a corollary of the GAT rule above — it blames +> `AsyncFnMut::CallRefFuture<'a>`, a generic associated type in the std +> definition. Plain `Fn`/`FnMut`/`FnOnce` have no such member and are dyn +> compatible, which is why `Box u32>` works fine. + +> **See also**: the RPITIT section below uses `-> impl Trait` in a trait +> definition — convenient, but it costs the trait its dyn compatibility. +> [Async Book — Ch 10](../async-book/ch10-async-traits.html) covers the +> `async fn` half and the `Pin>` workaround in depth. + ### Trait Objects Under the Hood — vtables and Fat Pointers A `&dyn Trait` (or `Box`) is a **fat pointer** — two machine words: @@ -610,6 +640,11 @@ impl Container for FixedFields { > **Before Rust 1.75**, you had to use `Box` or an associated > type to achieve this in traits. RPITIT removes the allocation. +> +> **But it costs dyn compatibility**: `-> impl Trait` is an opaque return type, +> so `dyn Container` is rejected (see [Dyn Compatibility](#dyn-compatibility-formerly-object-safety), +> rule 7). If you need the trait object, keep returning +> `Box + '_>` and pay the allocation. #### `impl Trait` vs `dyn Trait` — Decision Guide From 127c3fb20179344d5e7538bc702d8ac298d975c2 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Fri, 21 Aug 2026 05:01:31 -0300 Subject: [PATCH 9/9] =?UTF-8?q?docs(rust-patterns-book):=20note=20the=20ob?= =?UTF-8?q?ject=20safety=20=E2=86=92=20dyn=20compatibility=20rename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Readers will meet "object safe" everywhere outside this book — older articles, crate docs, Stack Overflow — and there is nothing in the chapter telling them it's the same concept under a different name. Follows the chapter's existing habit of anchoring features to versions ("Since Rust 1.65", "Since Rust 1.75"), and explains why the term changed: Rust has no OOP objects, and the property was never about memory safety. Co-Authored-By: Claude Opus 5 --- rust-patterns-book/src/ch02-traits-in-depth.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust-patterns-book/src/ch02-traits-in-depth.md b/rust-patterns-book/src/ch02-traits-in-depth.md index af2e984..e1b43f3 100644 --- a/rust-patterns-book/src/ch02-traits-in-depth.md +++ b/rust-patterns-book/src/ch02-traits-in-depth.md @@ -266,6 +266,13 @@ This connects directly to the **type-state pattern** in Chapter 3. ### Dyn Compatibility (formerly "Object Safety") +> **A note on the name**: this was called *object safety* until Rust 1.84. The +> term was misleading on both halves — Rust has no "objects" in the OOP sense, and +> nothing here is about memory safety. The question is simply "can this trait be +> used as `dyn Trait`?". The compiler now says `the trait 'X' is not dyn +> compatible` (still error `E0038`), but most existing articles, crate docs and +> Stack Overflow answers you'll find still say "object safe" — same concept. + Not every trait can be used as `dyn Trait`. A trait is **dyn compatible** only if: 1. **`Sized` must not be a supertrait** — i.e. the trait must not require `Self: Sized`