Chapter 7: Traits: Making different types do the same thing
This introduction previews the chapter’s trait topics without explaining how traits work.
After this chapter you will be able to
- Traits as behavioral guarantees. Explain what it means for a type to implement a trait and why that lets code rely on the type’s behavior.
- Matching trait method signatures. Describe how a trait specifies behavior through methods and why an implementing type may change a method’s behavior but not its signature.
- Required and default trait methods. Distinguish a trait method declared only by its signature from one with a default implementation, and explain when to choose each.
- Custom Display formatting. Describe why and how the section implements Display for Cat to control its printed appearance.
- Debug bound on self. Explain why a trait method cannot assume much about &self, and how requiring Debug lets a default method print the implementing value with {:?}.
- Required and default behavior. Distinguish the unwritten take_damage method from the written-out display_self method in MonsterBehavior, including why one is left to implementers and the other can have a default body.
- Methodless traits as bounds. Explain how a trait with no methods can act as a bound on a function argument, using the lawyer example to show why a type must implement the required traits before it can be passed in.
- Traits as type qualifications. Explain why it is useful to think of a trait as a qualification that a type may have, rather than treating a trait as a class.
- Trait bounds on functions. Describe how a function can use one or more traits as bounds on the types it accepts.
- From source and target types. Explain that a From implementation specifies which source type becomes which target type, and that the implementer decides how the conversion happens. Describe the section’s intended custom conversion from a vector of City values to Country.
- Orphan rule boundaries. Distinguish the trait implementations the orphan rule allows from the one it forbids: you may implement your trait for another type or another trait for your type, but not another trait for another type.
- Newtype trait implementation. Explain how wrapping someone else’s type in a tuple struct lets you implement traits on the resulting type, and why that type does not automatically have the wrapped type’s traits.
- Shared string reference bound. Explain why a generic function can use AsRef<str> to accept both String and &str, and how this differs from converting to another owned type.
- Printing through as_ref. Trace why printing a generic T with only an AsRef<str> bound fails, and how calling .as_ref() makes the example work.
What it builds on
- Display and Debug (chapter 1) · The earlier introduction to Debug.
- Implementing structs and enums (chapter 4) · Methods implemented on structs and enums.
- Mutable references (chapter 2) · A mutable reference allows a value to be changed.
- Generics (chapter 5) · Generics allow code to work with more than one concrete type.
- Vectors (chapter 3) · What a Vec is.
- Structs (chapter 4) · The reader has encountered structs before this tuple-struct-based idea.
Section by section
Introduction
This introduction previews the chapter’s trait topics without explaining how traits work.
7.1 Traits: The basics
Introduces traits as guarantees of a type’s behavior, contrasts derived and manual implementations, and shows why an implementation must follow a trait method’s signature.
- Traits as behavioral guarantees: Explain what it means for a type to implement a trait and why that lets code rely on the type’s behavior.
Why it matters: This is the basic reason traits let different types be treated as capable of the same behavior. - Derived and manual implementations: Distinguish deriving Debug from manually implementing a trait such as Add, and explain why Add cannot be derived in the example.
Why it matters: It explains why some trait behavior can be generated for you while other behavior needs your decisions. - Matching trait method signatures: Describe how a trait specifies behavior through methods and why an implementing type may change a method’s behavior but not its signature.
Why it matters: A shared method signature lets types supply different behavior while keeping the same method contract.
Key terms: trait, implement, methods, #[derive(Debug)], Add, impl, derive, method signature, fn run(&self)
7.1.1 All you need are the method signatures
This section explains when trait methods need only signatures, then uses a custom Display implementation to show how traits give types shared abilities.
- Required and default trait methods: Distinguish a trait method declared only by its signature from one with a default implementation, and explain when to choose each.
Why it matters: This choice determines which behavior a trait provides and which behavior each implementing type must supply. - Custom Display formatting: Describe why and how the section implements Display for Cat to control its printed appearance.
Why it matters: Implementing an existing trait lets a type participate in familiar formatting while controlling the result. - Display and ToString connection: Explain the additional String-conversion ability the section says comes from implementing Display.
Why it matters: One trait implementation can provide another useful ability without a separate implementation in this example. - Traits as shared behavior: Explain what traits express about different types, using an ability named in the section.
Why it matters: Thinking in terms of shared abilities explains why the same trait can be useful across different types.
Key terms: trait, method signature, default method, Display, Debug, Cat, write!, ToString, blanket implementation, .to_string()
7.1.2 More complex examples
This section uses a game example to show how trait bounds make default methods useful and how trait-based parameters can replace a concrete monster type.
- Debug bound on self: Explain why a trait method cannot assume much about &self, and how requiring Debug lets a default method print the implementing value with {:?}.
Why it matters: A bound tells a default trait method which abilities it can rely on. - Required and default behavior: Distinguish the unwritten take_damage method from the written-out display_self method in MonsterBehavior, including why one is left to implementers and the other can have a default body.
Why it matters: A trait can require type-specific work while supplying behavior supported by its bounds. - Trait-based damage targets: Explain why a fight method that takes a concrete Monster is limiting and how taking a type that implements a damage-taking trait would broaden it.
Why it matters: Depending on damage-taking behavior rather than one struct makes the game methods usable with more kinds of targets.
Key terms: &self, trait bounds, Debug, {:?}, MonsterBehavior, .take_damage(), .display_self(), &mut self, Monster, concrete type
7.1.3 Traits as bounds
This section explains that a trait can have no methods and still restrict which types a function accepts.
- Methodless traits as bounds: Explain how a trait with no methods can act as a bound on a function argument, using the lawyer example to show why a type must implement the required traits before it can be passed in.
Why it matters: Trait bounds can express which types a function accepts without adding methods to those types.
Key terms: trait, trait bound, impl
7.1.4 Traits are like qualifications
This section presents traits as qualifications that types can have and shows how functions use those qualifications as bounds.
- Traits as type qualifications: Explain why it is useful to think of a trait as a qualification that a type may have, rather than treating a trait as a class.
Why it matters: Thinking in terms of qualifications helps you decide which traits make sense for a type. - Trait bounds on functions: Describe how a function can use one or more traits as bounds on the types it accepts.
Why it matters: Trait bounds let a function state the qualifications its input needs without naming one specific type.
Key terms: trait, implement, qualification, bound
7.2 The From trait
The section uses Vec conversions to show how From converts between types and how to define a conversion for your own types.
- From source and target types: Explain that a From implementation specifies which source type becomes which target type, and that the implementer decides how the conversion happens. Describe the section’s intended custom conversion from a vector of City values to Country.
Why it matters: It helps you recognize what a From conversion promises without assuming every conversion works the same way. - Vec conversion results: Distinguish the Vec produced from an array of numbers from the Vec produced from a &str or String in the section’s examples.
Why it matters: The resulting Vec’s contents and element type depend on the particular From implementation.
Key terms: From, City, Country, Vec::from(), &str, String, Vec<u8>, bytes
7.3 The orphan rule
This section explains which trait implementations Rust’s orphan rule permits, why the restriction exists, and the basic newtype workaround.
- Orphan rule boundaries: Distinguish the trait implementations the orphan rule allows from the one it forbids: you may implement your trait for another type or another trait for your type, but not another trait for another type.
Why it matters: This tells you which trait implementations you can write directly. - Consistent behavior rationale: Explain why Rust prevents implementations where neither the trait nor the type is yours, using the section’s example of conflicting Display behavior.
Why it matters: The rule protects users from encountering incompatible behavior for what they expect to be the same type. - Newtype workaround idea: Describe the basic workaround given here: wrap someone else’s type in a tuple struct to create a new type.
Why it matters: It points to a way to work with an external type without implementing a trait directly on that external type.
Key terms: orphan rule, trait, type, Display, tuple struct, newtype idiom
7.4 Getting around the orphan rule with newtypes
The section shows how wrapping another type in a tuple struct creates a new type on which you can implement traits, while keeping the wrapped type’s traits and methods separate.
- Newtype trait implementation: Explain how wrapping someone else’s type in a tuple struct lets you implement traits on the resulting type, and why that type does not automatically have the wrapped type’s traits.
Why it matters: A wrapper gives you a type you control, with the traits you choose to give it. - Wrapped String access: Describe how the example accesses the String inside File and why File cannot automatically be used like a String.
Why it matters: Keeping the wrapper distinct also affects how code reaches and uses its inner value.
Key terms: newtype idiom, tuple struct, traits, #[derive], impl block, File, String, .0, pub
7.5 Taking a String and a &str in a function
Shows how to accept both String and &str in a generic function by requiring AsRef<str> and calling .as_ref() when a &str is needed.
- Shared string reference bound: Explain why a generic function can use AsRef<str> to accept both String and &str, and how this differs from converting to another owned type.
Why it matters: This bound lets one function work with both forms of string input without accepting every printable type. - Printing through as_ref: Trace why printing a generic T with only an AsRef<str> bound fails, and how calling .as_ref() makes the example work.
Why it matters: It shows the difference between requiring a trait on an input and using the value that trait provides.
Key terms: String, &str, AsRef<str>, trait bound, Display, .as_ref()
7.6 Summary
Where this is used later
- Attributes (chapter 13) · What it means for a type to implement a trait.
- Using a Box to handle multiple error types (chapter 13) · Implementing traits for types
- Iterators (chapter 8) · Implementing the requirements of a trait.
- Information on traits (chapter 13) · Traits specify methods that an implementation must provide.
- Blanket trait implementations (chapter 17) · Revisits the blanket implementations named here.
- Lifetimes in functions (chapter 10) · Provides the comparison with a generic trait bound.
- Regular generics compared to impl Trait (chapter 12) · Traits used as bounds
- thiserror (chapter 17) · The role of the From trait in conversions.
Concepts explained in this chapter
- traitA Rust trait is an abstract interface that defines capabilities or behavior a type can provide.
- type signatureA function signature is the declaration-level specification of a function's parameters and return type, including the corresponding specification for a method.
- trait boundA trait bound is a Rust constraint requiring a type to implement one or more specified traits before it can be used in a bounded context.
- DisplayDisplay printing formats a Rust value using its user-facing Display formatting behavior.
- add traitThe Rust Add trait defines how a type participates in addition with another value.
- attributeA struct field is a named or positional component of a Rust struct whose visibility can be declared independently of the struct type.
- impl blockAn impl block is Rust syntax that defines methods or trait implementations associated with a type.
- methodA method is a function associated with a type that is invoked through an instance or type and may receive the instance through self, &self, or &mut self.
- From traitThe From trait defines conversion of one Rust type into another specified type.
- DerefDeref is the Rust standard trait that defines immutable dereferencing and enables automatic dereference coercions for method and field access.
- derive macroA derive attribute is a Rust attribute that requests automatic implementation of specified traits for a type.
- blanket implementationA blanket implementation is an implementation of a trait for every type satisfying specified bounds or implementing another trait.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
