Chapter 15: Default, the builder pattern, and Deref
The introduction explains why builder-style method chaining and Deref can be useful for creating and extending types.
After this chapter you will be able to
- Builder pattern purpose. Explain that the builder pattern lets users create a value by chaining methods rather than supplying all a struct’s parameters at once, and that it lets the type’s author control which parts users can touch.
- Deref method reuse. Explain that Deref can let a type containing another type use that contained type’s methods, while also having its own methods.
- Sensible default values. Explain that implementing Default gives a struct or enum a common or base-state value that callers can create without arguments, as with Character::default().
- Reasons to implement Default. Justify implementing Default rather than only writing a no-argument constructor: other code can require the trait, and its presence gives users a conventional way to create an instance without choosing settings.
- Builder Defaults and Customization. Explain how the builder pattern lets someone start with a type’s default values and chain methods to change selected fields, and why this is useful for types with many fields.
- Owned builder method updates. Explain how a builder method takes mut self and a value, changes a field, and returns self. Distinguish this owned self from a mutable reference.
- Chained character construction. Describe why returning Self from each builder method lets the methods be chained to construct a Character.
- Final builder validation. Explain how .build() checks a Character's values and returns a Result, giving the character inside Ok(Self) when the checks pass.
- Limits of the usability flag. Describe how can_use is used to encourage a final .build() check and why it cannot force library users to perform that check.
- Separate CharacterBuilder type. Explain why CharacterBuilder is a different type from Character and how .try_build() turns it into a Character only when its parameters are acceptable.
- Newtype access and Deref motivation. Explain why a File tuple struct that holds a String cannot use String’s methods directly, and why access through .0 may not be available from another module. State that this access problem motivates using the Deref trait, without describing how to implement it.
- Dereferencing references with Deref. Explain that Deref enables the * operator to access a value through a reference, and why a reference must be dereferenced before it can be compared with the value in the example.
- Deref for a custom struct. Explain why * does not work on HoldsANumber yet and why implementing Deref would be useful instead of accessing its inner u8 directly.
- Access to the wrapped number. Explain what implementing Deref enables for HoldsANumber: using * to access its inner number without writing .0, and calling u8 methods while retaining its own methods. Describe how the example's checked_sub method returns Some or None depending on whether subtraction stays within bounds.
- DerefMut dependency and signature. Explain why Deref must be implemented before DerefMut and how deref_mut differs from deref.
- Appropriate use of Deref. Explain why implementing Deref and DerefMut for a Character’s hit points makes code confusing even though Rust accepts it, and describe the clearer alternative suggested here.
What it builds on
- Structs (chapter 4) · The reader has encountered structs.
- Traits: The basics (chapter 7) · The reader has encountered traits.
- Traits as bounds (chapter 7) · Traits can be requirements on types.
- Implementing structs and enums (chapter 4) · Implementing methods on structs.
- Chaining methods (chapter 8) · Chaining method calls.
- Getting around the orphan rule with newtypes (chapter 7) · Assumes the newtype pattern of wrapping a value in a tuple struct.
- More on references (chapter 2) · Earlier use of references and dereferencing
Section by section
Introduction
The introduction explains why builder-style method chaining and Deref can be useful for creating and extending types.
- Builder pattern purpose: Explain that the builder pattern lets users create a value by chaining methods rather than supplying all a struct’s parameters at once, and that it lets the type’s author control which parts users can touch.
Why it matters: This explains why a builder can make a type easier for other people to use. - Deref method reuse: Explain that Deref can let a type containing another type use that contained type’s methods, while also having its own methods.
Why it matters: This gives a reason to put an existing type inside one of your own types.
Key terms: builder pattern, chaining method after method, struct, Deref trait, methods
15.1 Implementing Default
This section shows how Default provides a sensible starting value for a type and why implementing the trait is useful beyond saving constructor arguments.
- Sensible default values: Explain that implementing Default gives a struct or enum a common or base-state value that callers can create without arguments, as with Character::default().
Why it matters: A sensible default gives callers an easy starting point without making them choose every value. - Reasons to implement Default: Justify implementing Default rather than only writing a no-argument constructor: other code can require the trait, and its presence gives users a conventional way to create an instance without choosing settings.
Why it matters: Implementing the trait makes the starting value useful to other Rust code, not just to callers of your own constructor. - Derived defaults for structs: Describe when a struct can use #[derive(Default)] and what Size::default() produces in the section’s example.
Why it matters: Deriving Default can avoid writing an implementation when every field already has a default.
Key terms: Default trait, default(), base state, Default, trait, .unwrap_or_default(), new(), #[derive(Default)], f64, Size::default()
15.2 The builder pattern
Introduces the builder pattern as a readable way to start with default values and selectively customize a type, especially one with many fields.
- Builder Defaults and Customization: Explain how the builder pattern lets someone start with a type’s default values and chain methods to change selected fields, and why this is useful for types with many fields.
Why it matters: It makes types with many optional settings easier to configure and read.
Key terms: builder pattern, default values, chain method after method
15.2.1 Writing builder methods
This section shows how builder methods take ownership of a struct, change one field, and return the struct so method calls can be chained.
- Owned builder method updates: Explain how a builder method takes mut self and a value, changes a field, and returns self. Distinguish this owned self from a mutable reference.
Why it matters: The method signature explains how each builder call changes and passes along the character. - Chained character construction: Describe why returning Self from each builder method lets the methods be chained to construct a Character.
Why it matters: Returning the character makes several field changes readable as one sequence of method calls.
Key terms: mut self, Self, builder method, chain methods, Character
15.2.2 Adding a final check to the builder pattern
This section adds a final validation step to a Character builder and explains why a usability flag cannot enforce valid construction.
- Final builder validation: Explain how .build() checks a Character's values and returns a Result, giving the character inside Ok(Self) when the checks pass.
Why it matters: A value can have the right Rust type and still be unsuitable for the game. - Limits of the usability flag: Describe how can_use is used to encourage a final .build() check and why it cannot force library users to perform that check.
Why it matters: A flag that callers may ignore is not a guarantee that every constructed Character is valid.
Key terms: builder pattern, .build(), Result, Ok(Self), u32, .to_lowercase(), can_use, Character
15.2.3 Making the builder pattern more rigorous
The section shows how a separate builder type can prevent unvalidated Character values and explains why builder methods help control how people use a type.
- Separate CharacterBuilder type: Explain why CharacterBuilder is a different type from Character and how .try_build() turns it into a Character only when its parameters are acceptable.
Why it matters: Keeping the builder and finished value distinct means a proposed Character is not treated as a valid Character before it passes the check. - Predictable type usage: Explain why the author values the builder pattern beyond convenient method names: starting with Default and adding small methods gives the type’s author control over how others use it.
Why it matters: A builder is useful not just for readable calls, but for shaping how others create and use your type.
Key terms: CharacterBuilder, Character, .try_build(), builder pattern, Default
15.3 Deref and DerefMut
This section explains why a newtype wrapping a String may need Deref when its inner value cannot be accessed directly.
- Newtype access and Deref motivation: Explain why a File tuple struct that holds a String cannot use String’s methods directly, and why access through .0 may not be available from another module. State that this access problem motivates using the Deref trait, without describing how to implement it.
Why it matters: It gives you a reason to use Deref before you learn how it works.
Key terms: Deref trait, newtype pattern, File, String, .0, mod
15.3.1 Deref basics
Introduces Deref as the trait behind * dereferencing and explains why a number-holding struct cannot be dereferenced until it implements the trait.
- Dereferencing references with Deref: Explain that Deref enables the * operator to access a value through a reference, and why a reference must be dereferenced before it can be compared with the value in the example.
Why it matters: This connects the familiar * operator to the trait the section is introducing. - Deref for a custom struct: Explain why * does not work on HoldsANumber yet and why implementing Deref would be useful instead of accessing its inner u8 directly.
Why it matters: It explains the practical reason for giving the custom type dereferencing behavior. - Smart pointer characteristics: Describe what the section calls a smart pointer and identify some types it gives as examples.
Why it matters: It places the proposed HoldsANumber behavior alongside types the reader has already used.
Key terms: Deref, dereference, reference, HoldsANumber, Box, pub, smart pointer, metadata, Vec, Rc
15.3.2 Implementing Deref
Shows how implementing Deref lets HoldsANumber expose its wrapped u8 through * and u8 methods, while noting that Deref alone does not allow mutation.
- Access to the wrapped number: Explain what implementing Deref enables for HoldsANumber: using * to access its inner number without writing .0, and calling u8 methods while retaining its own methods. Describe how the example's checked_sub method returns Some or None depending on whether subtraction stays within bounds.
Why it matters: It explains why a wrapper can be used both as its own type and, for these operations, like the number it holds. - Limit of Deref alone: Explain why implementing Deref alone does not let the example change the number inside HoldsANumber from 20 to 30 through dereferencing.
Why it matters: It separates reading through a dereference from changing the wrapped value.
Key terms: Deref, HoldsANumber, u8, .checked_sub(), Some, None, mutable access, DerefMut
15.3.3 Implementing DerefMut
This subsection explains how DerefMut builds on Deref to provide mutable access, then shows why Deref can be useful for wrapper types.
- DerefMut dependency and signature: Explain why Deref must be implemented before DerefMut and how deref_mut differs from deref.
Why it matters: It explains how a type that already supports Deref can also provide mutable access. - Wrapper methods and type safety: Explain how implementing Deref can let a wrapper use the inner type’s methods while remaining a distinct type.
Why it matters: It shows why a distinct wrapper type can still be convenient to use.
Key terms: DerefMut, Deref, Self::Target, deref_mut(), type safety, Email, Quantity
15.3.4 Using Deref the wrong way
This section uses a deliberately confusing Character example to explain why Deref should generally be reserved for smart pointers.
- Appropriate use of Deref: Explain why implementing Deref and DerefMut for a Character’s hit points makes code confusing even though Rust accepts it, and describe the clearer alternative suggested here.
Why it matters: Code can compile and still obscure what it does for the next person reading it.
Key terms: Deref, DerefMut, smart pointers, hit_points
15.4 Summary
Where this is used later
- std::mem (chapter 21) · The Default trait and default values.
- Using OpenOptions to work with files (chapter 18) · The builder pattern used to set options.
- Further development and cleanup (chapter 23) · The builder pattern used in the ratatui setup
Concepts explained in this chapter
- builder patternThe builder pattern is a construction pattern that uses a separate configurable object to assemble and validate the parameters needed to create another type.
- DerefDeref is the Rust standard trait that defines immutable dereferencing and enables automatic dereference coercions for method and field access.
- DerefMutDerefMut is a Rust standard trait that defines mutable dereferencing behavior for a type and requires the type to implement Deref.
- DefaultDefault is the Rust standard trait that defines how to construct a default value for a type.
- smart pointerA smart pointer is a pointer-like data structure that manages an owned or referenced value and provides behavior beyond raw address storage.
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.
