-
Book Overview & Buying
-
Table Of Contents
Design Patterns and Best Practices in Rust
By :
Rust has a way of drawing people in. You know the sales pitch: it has memory safety, fearless concurrency, zero-cost abstractions, and quite a few more things. It has a somewhat justified reputation for being a language where "if it compiles, it works." More than that, if you are reading this book, you have probably experienced how much fun it can be. Working in the language feels like a different, and sometimes delightful, experience.
You've probably also had some not-at-all fun moments. Code that would be straightforward in Java or Python suddenly won't compile. The borrow checker rejects seemingly reasonable designs. You reach for familiar patterns from object-oriented programming and find that they lead to frustrating dead ends: fighting with lifetimes, scattering clone() calls everywhere, or wrapping everything in Rc<RefCell<T>> just to make the compiler stop complaining. The code compiles, but it feels wrong. You know there must be a better way.
There is. But it requires something more than learning new syntax or memorizing a different set of rules. Truly effective Rust development requires a transformation in how you approach software design itself. The patterns and techniques that serve you well in other languages are not just syntactically different in Rust; they are conceptually different. Internalizing Rust's philosophy and allowing it to shape your thinking matters more than any individual pattern or technique.
As an experienced developer, you already have a toolbox full of approaches that have always worked. In Rust, you need a new toolbox. Some of the tools will be familiar. Some will need adaptation. Some will be entirely new. And perhaps most surprisingly, the constraints that initially feel limiting – ownership, borrowing, and lifetimes – turn out to guide you toward better designs. When you stop fighting the compiler and start listening to it, you discover that it's the most helpful collaborator you've ever had.
This book charts that transformation through three hands-on projects. We begin with a deliberately broken "Bad Calculator" that demonstrates what goes wrong when you try to write Java or C++ in Rust syntax. We then rebuild it from scratch as the "Correct Calculator," adapting the classic Gang of Four design patterns to work with Rust's ownership model and type system rather than against them. Finally, we build "Samsa," a publish/subscribe microservice, using patterns that are uniquely Rust: patterns that leverage the type system to catch errors at compile time, that use functional programming idioms as a natural part of the language, and that turn Rust's unique features into architectural advantages. By the end, you won't just know how to apply patterns in Rust. You'll think in Rust.
What this book does not cover
This book focuses on design patterns, architectural decisions, and idiomatic Rust. Several important topics fall outside that scope, including testing strategies, benchmarking, continuous integration, deployment, and async runtime selection. These are substantial subjects that deserve dedicated treatment rather than superficial coverage. The code repository that accompanies this book includes test suites for each chapter's examples, and we encourage readers to study them alongside the prose. Where a pattern has particular implications for testability, we note it briefly, but a thorough exploration of testing in Rust is beyond what we set out to do here.
This book is for experienced Rust programmers who have knowledge of the language, its tools, and its syntax. If you can build basic Rust applications and understand concepts such as ownership, borrowing, and lifetimes, this book will help you take the next step: structuring your code to perform common tasks and overcome common challenges in an idiomatic "Rusty" way.
You don't need prior knowledge of design patterns, though familiarity with object-oriented programming concepts from languages such as Java, C++, or Python will help you appreciate how Rust's approach differs. The book will help you avoid common mistakes made by applying patterns that don't work in Rust while guiding you to effective alternatives.
Chapter 1, Why Is Rust Different?, explores what makes Rust unique and why familiar patterns from other languages often don't work as expected. It introduces the "Bad Calculator" project that will serve as our cautionary tale throughout the first four chapters, and explores the common experience of hitting a wall when moving from simple Rust programs to more complex designs.
Chapter 2, Anti-Pattern: Designing for Object Orientation, examines the most common trap for developers coming from object-oriented languages: trying to recreate inheritance hierarchies in Rust. We explore why techniques like abusing Deref to simulate inheritance, misusing traits as interfaces, and building deep type hierarchies lead to brittle, unidiomatic code. You'll learn to recognize these patterns in your own code and understand why they fail.
Chapter 3, Anti-Pattern: Using Clone and Rc Everywhere, tackles the temptation to sidestep ownership by cloning everything or wrapping values in reference-counted interior mutability. While these tools have legitimate uses, reaching for them as a default leads to code that is slow, fragile, and difficult to reason about. We see how excessive cloning masks deeper design problems and how Rc<RefCell<T>> reintroduces the very bugs that Rust's ownership model is designed to prevent.
Chapter 4, Don't Fight the Borrow Checker, addresses the frustration of working against Rust's most distinctive feature. We examine workarounds like excessive unsafe code, mutable statics, and lifetime annotation gymnastics, showing why each creates more problems than it solves. More importantly, we see how to listen to the borrow checker's feedback and redesign data structures so that ownership flows naturally.
Chapter 5, Creational Patterns: Making Things, begins the second major project: rebuilding our calculator correctly using proven design patterns. We cover Factory Methods, Abstract Factories, Builders, Singletons, and Prototypes, showing how Rust's ownership model, trait system, and module visibility make some of these patterns trivial to implement and others entirely unnecessary.
Chapter 6, Structural Patterns: Connecting and Aggregating Components, explores how to compose objects and build flexible interfaces using Decorators, Adapters, Facades, Composites, and the Bridge pattern. We examine how Rust's trait objects enable runtime polymorphism for structural patterns, why Proxies and Flyweights are largely handled by Rust's built-in features, and how the ownership model shapes each pattern's implementation.
Chapter 7, Behavioral Patterns 1: Taking Action, covers patterns for performing actions, processing requests, and selecting algorithms at runtime. We implement Commands, Chain of Responsibility, Strategy, Mediator, and Template Method, showing how Rust's closures, enums, and trait system provide powerful alternatives to the class-based implementations found in other languages.
Chapter 8, Behavioral Patterns 2: Keeping Track, completes the Correct Calculator with patterns for maintaining and inspecting state. We implement Iterators, State Machines, Memento for undo/redo, Observer for event handling, and Visitor for expression traversal. Several of these patterns leverage Rust's built-in features: the iterator trait, pattern matching, and enum-based state machines.
Chapter 9, Architectural Patterns, begins the third and most ambitious project: Samsa, a publish/subscribe microservice. We establish high-level design principles for structuring Rust applications, including data flow architecture, containing mutability at system boundaries, leveraging modules for encapsulation, and structuring data so that ownership and borrowing work cleanly.
Chapter 10, Patterns That Leverage the Type System, introduces patterns unique to Rust that use the type system as an active development tool. NewType wrappers add semantic meaning without runtime cost. Parse, Don't Validate encodes data validity in types. The TypeState pattern makes illegal state transitions impossible at compile time. Sealed Traits restrict trait implementation to controlled sets of types.
Chapter 11, Patterns from Functional Programming, explores Rust's functional programming capabilities as practical design tools. We cover function composition and pipelines, using generics as type classes, advanced pattern matching for complex data transformations, and closures as first-class architectural components.
Chapter 12, Patterns Emerging from Rust's Core Features, examines patterns built on features that have no direct equivalent in other languages. We explore advanced Result and Option composition, block expressions as scoping tools, RAII patterns for resource management through Rust's Drop trait, and techniques for writing code that is both safe and concise.
Chapter 13, Leaning into Rust, steps back from specific patterns to reflect on the transformation across all three projects. We examine what the Bad Calculator, Correct Calculator, and Samsa taught us about working with Rust rather than against it, and we articulate the principles of Rust-native thinking that emerge from the journey.
You should be comfortable writing basic Rust programs and familiar with core concepts such as ownership, borrowing, lifetimes, traits, and enums. Experience with the Rust toolchain (cargo, rustc, rustfmt, clippy) is assumed. While we explore design patterns as we encounter them, some familiarity with object-oriented programming concepts will help you appreciate how Rust's approach differs.
|
Software/hardware covered in the book |
Operating system requirements |
|---|---|
|
Rust 1.78.0 or later |
Windows, macOS, or Linux |
|
Cargo (included with Rust) |
|
|
A text editor or IDE with Rust support |
All code examples have been tested with stable Rust. It is recommended to use rustup to install and manage your Rust toolchain. You can find it at https://rustup.rs/ with instructions for installation on all major platforms.
If you are using the digital version of this book, it is advisable to type the code yourself or access the code from the book's GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.
The three example projects build progressively across chapters. The Bad Calculator (Chapters 1–4), Correct Calculator (Chapters 5–8), and Samsa microservice (Chapters 9–12) each build on what came before. While you can read individual chapters for reference, working through the projects from start to finish will give you the best understanding of how the patterns work together in real applications and how the transformation in design thinking unfolds.
The code bundle for the book is hosted on GitHub at https://github.com/PacktPublishing/Design-Patterns-and-Best-Practices-in-Rust. We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing. Check them out!
We also provide a PDF file that has color images of the screenshots/diagrams used in this book. You can download it here: https://packt.link/gbp/9781836209478.
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. For example: "Here, CalculationResult is our container for results."
A block of code is set as follows:
impl HistoryViewer for Calculator {
fn view_history(&self) -> &[CalculationResult] {
&self.history
}
fn get_last_result(&self) -> Option<f64> {
self.history.last().map(|r| r.result)
}
}
Any command-line input or output is written as follows:
error[E0046]: not all trait items implemented, missing: `apply`
--> src/main.rs:47:1
|
13 | fn apply(&mut self) -> Box<dyn Operand>;
| ---------------------------------------- `apply` from trait
...
47 | impl Operator for AdditionOperator {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `apply` in implementation
Bold: Indicates a new term, an important word, or words that you see on the screen. For instance, words in menus or dialog boxes appear in the text like this. For example: "We will deal with common issues caused by treating Rust as if it were an object-oriented (OO) language such as Java or C++ and explore common errors from this kind of thinking."
Warnings or important notes appear like this.
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book or have any general feedback, please email us at customercare@packt.com and mention the book's title in the subject of your message.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you reported this to us. Please visit http://www.packt.com/submit-errata, click Submit Errata, and fill in the form.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit http://authors.packt.com/.
Change the font size
Change margin width
Change background colour