docs: object safety → dyn compatibility, and fix the rule list in rust-patterns-book ch02 - #120
Open
Abinoam P. Marques Jr. (abinoam) wants to merge 9 commits into
Conversation
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 <noreply@anthropic.com>
…atibility The rule said "No use of `Self` in return position (except via indirection like `Box<Self>`)". 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<Self>` is not an escape hatch in return position. `Box<Self>` is a valid *receiver* (`self: Box<Self>`); as a return type it still names `Self` and is rejected. The real idiom is `-> Box<dyn Trait>`, 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 <noreply@anthropic.com>
…t valid receivers
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>`, `self: Rc<Self>`, `self: Arc<Self>`,
`self: Pin<P>`. All six verified dispatching through a trait object on
rustc 1.95.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mpatibility 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 <noreply@anthropic.com>
…n dispatchable methods 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
…nc fn / impl Trait)
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<Item = &str>` as a strict
improvement over `Box<dyn Iterator>` ("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<Box<dyn Future>>` 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 <noreply@anthropic.com>
…rename
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 <noreply@anthropic.com>
Author
|
@microsoft-github-policy-service agree |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Some background, since this is my first contribution here. I've been programming for a long time, though in other stacks - Rust is the new one for me. I want to say first that this material is excellent: ch02 goes deeper than most trait material I've found.
While reading, I asked an AI assistant to sanity-check a few sections against the current Reference. The intent was just to confirm the object safety → dyn compatibility rename. It came back with more than that, so rather than take it at face value I put every rule through rustc 1.95.0 in a scratch project - a compiling example and a failing example per rule. Two of the chapter's four rules turned out to be genuinely wrong, and the failing cases produce the error messages quoted throughout.
I kept the commits atomic - drop whichever ones don't hold up and the rest still stand on their own.
Rust renamed object safety to dyn compatibility in 1.84. The rename missed the release notes, but it's settled everywhere that matters - the Reference, rustdoc, and the compiler itself, which now says
the trait 'X' is not dyn compatibleforE0038.While updating the terminology in
rust-patterns-bookch02, I checked the chapter's rule list against the Reference and found it's not only outdated wording: the list has 4 rules where the Reference has 6, and two of the 4 are factually wrong.Every rule touched here was verified against rustc 1.95.0 in a scratch project before being written - one example per rule, each with the compiling case and the failing case. The compiler messages quoted in the commits and in the prose are literal output, not paraphrase.
Commits
Each commit is one self-contained fix, so any of them can be dropped without disturbing the others.
docs: rename "object safety" to "dyn compatibility"async-bookch10, where line 80 already said "not dyn-compatible" while line 143 still said "not object-safe".fix: correct the Self-in-method ruleSelfin return position (except via indirection likeBox<Self>)". Both halves are wrong - see below.fix: correct the associated-function rule and list valid receivers&self,&mut self, orself)". Both halves are wrong - see below.fix: fix contradictory Sized advice in the rule of thumbSizedbounds" three lines after the Workarounds block recommends exactlywhere Self: Sized.docs: note that lifetime parameters are alloweddocs: add the supertrait rulesdocs: add associated const and GAT restrictionsdocs: add the opaque return type restrictionasync fn, no return-positionimpl Trait.docs: note the renameThe two factual errors
Rule 3 -
Selfis not only about return position. The Reference says a dispatchable method must "not useSelfexcept in the type of the receiver", which covers arguments too. The old wording could not explain whyPartialEq(fn eq(&self, other: &Self)) isn't dyn compatible. rustc emits two distinct messages here:Rule 3 -
Box<Self>is not an escape hatch.Box<Self>is a valid receiver (self: Box<Self>), not a valid return type;fn spawn(&self) -> Box<Self>is still rejected. The actual "clone through a trait object" idiom is-> Box<dyn Trait>, which names a concrete type rather thanSelf.Rule 4 - associated functions are allowed. They just need
where Self: Sizedto be opted out of the vtable. rustc suggests this fix itself in theE0038note.Rule 4 -
selfby value is not a dispatchable receiver. It implieswhere Self: Sized. A trait with aself-by-value method is still dyn compatible - the method is simply absent from the vtable. Calling it through the trait object fails withE0161: cannot move a value of type dyn Consume, notE0038. The rule now lists the real receiver set:&self,&mut self,self: Box<Self>,self: Rc<Self>,self: Arc<Self>,self: Pin<P>- all six verified dispatching through a trait object.Two things worth flagging for review
The GAT and RPITIT rules land close to home. The chapter teaches GATs (
LendingIterator) and RPITIT (fn items(&self) -> impl Iterator<..>, presented as a strict improvement overBox<dyn Iterator>) without mentioning that both cost the trait its dyn compatibility. Commits 7 and 8 add cross-references in both directions.Associated constants have no
where Self: Sizedopt-out. Readers will try it by analogy with methods. It isn't accepted syntax on stable -E0658, generic const items are experimental (rust#113521). The workaround is to turn the constant into a method, which the text now says.Verification
cargo xtask build- 7/7 books build clean.<ol>, the section anchor resolves, and the new cross-reference toasync-bookch10 points at a file that exists.SUMMARY.mdor internal link referenced the old#trait-object-safety-rulesanchor, so renaming the heading breaks nothing in-repo.