Statistics
3
Views
0
Downloads
0
Donations
Support
Share
Uploader

高宏飞

Shared on 2026-03-26

AuthorJohn Arundel

About the book This friendly, supportive, yet challenging book will show you how master software engineers think, and guide you through the process of designing production-ready command-line tools in Rust, step by step. This book is aimed at those who have a little experience with Rust (or even a lot), and would now like to learn how to build good software with it. What is “good” software anyway? What would it look like in Rust? And how do we get there from here?

Tags
No tags
Publisher: Bitfield Consulting
Publish Year: 2025
Language: 英文
Pages: 240
File Format: PDF
File Size: 2.7 MB
Support Statistics
¥.00 · 0times
Text Preview (First 20 pages)
Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

(This page has no text content)
Contents Praise for ‘The Secrets of Rust: Tools’ 11 Introduction 12 Who is this book for? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 What should I know before reading it? . . . . . . . . . . . . . . . . . . 13 How can I play along at home? . . . . . . . . . . . . . . . . . . . . . . . . . 13 How do I install Rust? . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 What editor or IDE should I use? . . . . . . . . . . . . . . . . . . . . . 14 Where do I find the listings and example solutions? . . . . . . . . . . . 14 What you’ll learn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1. Crates 16 Hello, world! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Hello as a service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 The simplest thing imaginable . . . . . . . . . . . . . . . . . . . . . . 17 Raiders of the lost crate . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Rust’s built‐in test framework . . . . . . . . . . . . . . . . . . . . . . . 19 Failing tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 A practice test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Verifying the test by “bebugging” . . . . . . . . . . . . . . . . . . . . . 21 Writing helpful failure messages . . . . . . . . . . . . . . . . . . . . . 22 Testing a function that prints . . . . . . . . . . . . . . . . . . . . . . . 22 A data‐oriented hello crate . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Modifying world to return a string . . . . . . . . . . . . . . . . . . . . 23 Over to you . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Variations on a theme . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Checking the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Test names should be sentences . . . . . . . . . . . . . . . . . . . . . 25 The real program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2. Paperwork 27 Counting lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 A prototype line counter . . . . . . . . . . . . . . . . . . . . . . . . . 28 Counting lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 What do users want? . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 The “magic function” approach . . . . . . . . . . . . . . . . . . . . . . 29 Testing count_lines . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Taking a BufRead parameter . . . . . . . . . . . . . . . . . . . . . . . 31 2
Checking the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Passing the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Moving tests into a module . . . . . . . . . . . . . . . . . . . . . . . . 33 Anatomy of a test module . . . . . . . . . . . . . . . . . . . . . . . . . 33 Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Readers are fallible . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 How lines handles errors . . . . . . . . . . . . . . . . . . . . . . . . 35 Does our program handle errors? . . . . . . . . . . . . . . . . . . . . . 35 Endless iteration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Errors on standard input . . . . . . . . . . . . . . . . . . . . . . . . . 36 Handling errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Fixing a bug with a test . . . . . . . . . . . . . . . . . . . . . . . . . . 37 A fallible reader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Returning a Result . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Handling errors in count_lines . . . . . . . . . . . . . . . . . . . . . 39 The iterator version . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 Displaying results to users . . . . . . . . . . . . . . . . . . . . . . . . 40 A slightly nicer presentation . . . . . . . . . . . . . . . . . . . . . . . 41 Standard error, and exit status . . . . . . . . . . . . . . . . . . . . . . 41 Resilience matters . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Handling invalid input . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3. Arguments 44 Taking a filename . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Getting command‐line arguments . . . . . . . . . . . . . . . . . . . . 45 What even are arguments? . . . . . . . . . . . . . . . . . . . . . . . . 45 Opening the file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Ownership what now? . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Handling the “can’t open” case . . . . . . . . . . . . . . . . . . . . . . 47 A rough first draft . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Throwing the paperwork away . . . . . . . . . . . . . . . . . . . . . . . . . 49 Returning a Result from main . . . . . . . . . . . . . . . . . . . . . . 49 Turning None into Err . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 The user’s view of errors . . . . . . . . . . . . . . . . . . . . . . . . . 50 Not all errors are io::Error . . . . . . . . . . . . . . . . . . . . . . . 51 Wanted: an “any old error” type . . . . . . . . . . . . . . . . . . . . . 51 Returning anyhow::Result . . . . . . . . . . . . . . . . . . . . . . . . 52 Adding context to errors . . . . . . . . . . . . . . . . . . . . . . . . . 52 Taking multiple arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 A simple prototype . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 A problem of ownership . . . . . . . . . . . . . . . . . . . . . . . . . 54 Challenge of a lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Making copies with clone . . . . . . . . . . . . . . . . . . . . . . . . . 56 Adding more context . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 The magic variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 3
A higher‐level abstraction . . . . . . . . . . . . . . . . . . . . . . . . . 59 Composing abstractions . . . . . . . . . . . . . . . . . . . . . . . . . 60 4. Flags 62 Counting words . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 A test for a word‐counting function . . . . . . . . . . . . . . . . . . . . 63 Implementing count_words . . . . . . . . . . . . . . . . . . . . . . . 64 An efficient line reader . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Using read_line for word counting . . . . . . . . . . . . . . . . . . . . 65 Updating count_lines . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Preparing for the refactoring . . . . . . . . . . . . . . . . . . . . . . . 67 The Default trait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 The refactored count function . . . . . . . . . . . . . . . . . . . . . . 69 Testing commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 Taking a flag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 Integration tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 Our first integration test . . . . . . . . . . . . . . . . . . . . . . . . . 71 Running the program from a test . . . . . . . . . . . . . . . . . . . . . 71 Introducing assert_cmd . . . . . . . . . . . . . . . . . . . . . . . . . 72 Asserting exit status . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Asserting specific output . . . . . . . . . . . . . . . . . . . . . . . . . 73 Avoiding fragile tests . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Testing the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 A little bebugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Testing the happy path . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Comparing output with expectations . . . . . . . . . . . . . . . . . . . 76 A test for the word‐counting flag . . . . . . . . . . . . . . . . . . . . . 77 Implementing the flag . . . . . . . . . . . . . . . . . . . . . . . . . . 78 The flag‐handling logic . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Running the word counter . . . . . . . . . . . . . . . . . . . . . . . . 80 Using clap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 More complications . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Flags and arguments can be a lot . . . . . . . . . . . . . . . . . . . . . 81 Introducing clap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Crate features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Deriving an argument parser . . . . . . . . . . . . . . . . . . . . . . . 82 Adding clapmetadata . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Using the parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Testing the parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 The --help flag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Documenting arguments . . . . . . . . . . . . . . . . . . . . . . . . . 86 clap is magic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 You don’t need clap, unless you do . . . . . . . . . . . . . . . . . . . . 87 4
5. Files 88 A logbook in Rust . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 A first experiment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 A quick reminder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Creating the file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 File descriptors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 File modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Specifying “open options” for files . . . . . . . . . . . . . . . . . . . . 91 Taking a message on the command line . . . . . . . . . . . . . . . . . 92 Joining space‐separated arguments . . . . . . . . . . . . . . . . . . . . 92 Reading the logbook . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Adding a user interface . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Reading an empty logbook . . . . . . . . . . . . . . . . . . . . . . . . 94 The prototype logbook . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Building a logbook library . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Reading the logbook . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Returning an Option . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 match versus if let Some . . . . . . . . . . . . . . . . . . . . . . . . 97 Appending a message . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Testing read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Mismatched types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Strings and things . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Slicing strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Implementing the “no file” behaviour . . . . . . . . . . . . . . . . . . 100 Implementing the “empty file” behaviour . . . . . . . . . . . . . . . . . 101 Implementing the “read file” behaviour . . . . . . . . . . . . . . . . . 101 Designing the “append” API . . . . . . . . . . . . . . . . . . . . . . . . 103 A first test for append . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Testing smarter, not harder . . . . . . . . . . . . . . . . . . . . . . . . . . 104 Checking assumptions . . . . . . . . . . . . . . . . . . . . . . . . . . 104 A feeble test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 What we’re really testing . . . . . . . . . . . . . . . . . . . . . . . . . 105 Cleanup and paperwork . . . . . . . . . . . . . . . . . . . . . . . . . 105 A self‐cleaning temporary directory . . . . . . . . . . . . . . . . . . . 105 Holding the wormhole open . . . . . . . . . . . . . . . . . . . . . . . 106 More string theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Introducing Path and PathBuf . . . . . . . . . . . . . . . . . . . . . . 107 The AsRef<T> trait . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Taking AsRef<Path> in append . . . . . . . . . . . . . . . . . . . . . . 108 Taking AsRef<Path> in read . . . . . . . . . . . . . . . . . . . . . . . 108 Implementing append . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Hello, Clippy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Testing the “append to file” behaviour . . . . . . . . . . . . . . . . . . 111 Bebugging append . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 The magic main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 A published crate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 5
The README file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 The crate documentation . . . . . . . . . . . . . . . . . . . . . . . . . 113 Inner doc comments . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Generating the docs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Outer doc comments . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Exploring the docs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Intra‐doc links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Documentation is design . . . . . . . . . . . . . . . . . . . . . . . . . 118 Crate metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Updating Cargo.toml . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 Publishing to crates.io . . . . . . . . . . . . . . . . . . . . . . . . . 120 Using the tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Updating the crate . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 6. Data 122 A persistent Vec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 Remember the milk . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 A quick sketch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 Designing open with a test . . . . . . . . . . . . . . . . . . . . . . . . 124 Validating the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 The “no data” case . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Implementing open . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 Collecting an iterator of Result . . . . . . . . . . . . . . . . . . . . . . 127 Writing sync . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 A round‐trip test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 sync: a first try . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Achievement unlocked: memos . . . . . . . . . . . . . . . . . . . . . 129 Creating a type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 A more useful abstraction . . . . . . . . . . . . . . . . . . . . . . . . . 130 Adding behaviour to a Vec . . . . . . . . . . . . . . . . . . . . . . . . 130 Adapting the tests: open . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Adapting the tests: sync . . . . . . . . . . . . . . . . . . . . . . . . . . 132 Refactoring open and sync for the new type . . . . . . . . . . . . . . . 133 Inherent implementations . . . . . . . . . . . . . . . . . . . . . . . . 134 Path to PathBuf: the From trait . . . . . . . . . . . . . . . . . . . . . . 134 as_ref: From AsRef to reference . . . . . . . . . . . . . . . . . . . . . 135 Building the data Vec . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 sync: the making of a method . . . . . . . . . . . . . . . . . . . . . . 136 A quick reality check . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Extending the abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 The definition of “done” . . . . . . . . . . . . . . . . . . . . . . . . . 137 Designing a Memo struct . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Defining a new enum type . . . . . . . . . . . . . . . . . . . . . . . . . 138 Serialization: from data to bytes . . . . . . . . . . . . . . . . . . . . . 139 Storing data as JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 Autogenerating serialization code with Serde . . . . . . . . . . . . . . . 140 6
Deriving Serialize / Deserialize . . . . . . . . . . . . . . . . . . . . 140 Updating the tests: open . . . . . . . . . . . . . . . . . . . . . . . . . 141 Implementing PartialEq . . . . . . . . . . . . . . . . . . . . . . . . . 142 Deriving Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 Updating the tests: sync . . . . . . . . . . . . . . . . . . . . . . . . . 143 Implementing sync using serde_json . . . . . . . . . . . . . . . . . . 144 Refactoring open to read JSON . . . . . . . . . . . . . . . . . . . . . . 145 Implementing Display for our types . . . . . . . . . . . . . . . . . . . 146 Creating a new Memo . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Constructing some test data . . . . . . . . . . . . . . . . . . . . . . . . 148 Using sync and open to test each other . . . . . . . . . . . . . . . . . . 148 Extending the user interface . . . . . . . . . . . . . . . . . . . . . . . . . . 150 A done flag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 Don’t make users do paperwork . . . . . . . . . . . . . . . . . . . . . . 150 Matching memos by substring . . . . . . . . . . . . . . . . . . . . . . 150 Adding a done flag . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 The magic find_all function . . . . . . . . . . . . . . . . . . . . . . . 152 Testing find_all . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Implementing find_all . . . . . . . . . . . . . . . . . . . . . . . . . 154 The user experience . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 Purging completed memos . . . . . . . . . . . . . . . . . . . . . . . . 155 The --purge flag in action . . . . . . . . . . . . . . . . . . . . . . . . 158 Playing well with others . . . . . . . . . . . . . . . . . . . . . . . . . . 158 7. Clients 159 A weather client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 The Weatherstack API . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Making HTTP requests with reqwest . . . . . . . . . . . . . . . . . . . 160 A first sketch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 The API response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 From sketch to crate . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Just like that: a magic get_weather function . . . . . . . . . . . . . . . 163 Testing get_weather . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 A failing implementation . . . . . . . . . . . . . . . . . . . . . . . . . 165 Designing for testability . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 A problem of determinism . . . . . . . . . . . . . . . . . . . . . . . . 165 What can we test about get_weather? . . . . . . . . . . . . . . . . . . 166 Eating the elephant one bite at a time . . . . . . . . . . . . . . . . . . . 166 Testing request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 Implementing request . . . . . . . . . . . . . . . . . . . . . . . . . . 168 Blocking or non‐blocking? . . . . . . . . . . . . . . . . . . . . . . . . 169 Extracting weather data from the response . . . . . . . . . . . . . . . . 169 Deserializing the JSON . . . . . . . . . . . . . . . . . . . . . . . . . . 170 A surfeit of structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 Using JSON Pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Implementing deserialize . . . . . . . . . . . . . . . . . . . . . . . 172 7
Taking it for a trial run . . . . . . . . . . . . . . . . . . . . . . . . . . 173 A nicer Display . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 Mixing arguments and environment variables . . . . . . . . . . . . . . 175 Mock testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 What could be wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . 177 Why don’t we want to make API calls in tests? . . . . . . . . . . . . . . 177 A fake server using httpmock . . . . . . . . . . . . . . . . . . . . . . . 178 Kicking the tyres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 Configuring routes on the mock server . . . . . . . . . . . . . . . . . . 179 Mocking the real API . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 Injecting the base URL . . . . . . . . . . . . . . . . . . . . . . . . . . 180 A provider abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . 181 Building the Weatherstack provider . . . . . . . . . . . . . . . . . . . 182 Compulsory return values with must_use . . . . . . . . . . . . . . . . . 183 get_weather becomes a method . . . . . . . . . . . . . . . . . . . . . 183 Putting Weatherstack to work . . . . . . . . . . . . . . . . . . . . . . 184 Bebugging the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 Checking the mock’s assertions . . . . . . . . . . . . . . . . . . . . . . 185 Putting it all together . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 Using mocks with care . . . . . . . . . . . . . . . . . . . . . . . . . . 187 Integration testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 Designing test‐friendly APIs . . . . . . . . . . . . . . . . . . . . . . . 188 Adding a “Fahrenheit” feature . . . . . . . . . . . . . . . . . . . . . . . . . 188 A ruthlessly minimal solution . . . . . . . . . . . . . . . . . . . . . . . 189 An into_fahrenheitmethod . . . . . . . . . . . . . . . . . . . . . . . 190 Dealing with quantities . . . . . . . . . . . . . . . . . . . . . . . . . . 191 The newtype pattern and tuple structs . . . . . . . . . . . . . . . . . . 191 Making Weather unit‐agnostic . . . . . . . . . . . . . . . . . . . . . . 192 A Temperature struct with internal units . . . . . . . . . . . . . . . . . 192 Converting between units . . . . . . . . . . . . . . . . . . . . . . . . . 193 Testing conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Handle units and quantities with care . . . . . . . . . . . . . . . . . . 194 Formatting temperatures for display . . . . . . . . . . . . . . . . . . . 195 Drawing the line . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 8. Commands 197 Running commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 A Cargo runner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 The Command type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 Running the command and getting the output . . . . . . . . . . . . . . 199 Understanding the output . . . . . . . . . . . . . . . . . . . . . . . . . 199 Converting the output to a string . . . . . . . . . . . . . . . . . . . . . 199 A working cargo runner . . . . . . . . . . . . . . . . . . . . . . . . . 200 Adding arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 A timer tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Using std::time::Instant . . . . . . . . . . . . . . . . . . . . . . . . 201 8
Timing between two points . . . . . . . . . . . . . . . . . . . . . . . . 201 Computations must be used . . . . . . . . . . . . . . . . . . . . . . . 202 Variables, too . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 Discarding values with _ . . . . . . . . . . . . . . . . . . . . . . . . . 202 A (very) basic benchmark . . . . . . . . . . . . . . . . . . . . . . . . . 202 Building the timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 How it works . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 Designing a timer crate . . . . . . . . . . . . . . . . . . . . . . . . . . 205 The magic function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206 Testing the timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206 Making the magic happen . . . . . . . . . . . . . . . . . . . . . . . . 206 Slimming world . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 The Rust build cache . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 Cleaning with Cargo . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 Automate the boring stuff . . . . . . . . . . . . . . . . . . . . . . . . . 208 The least we can do . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 The magic function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Eating smaller elephants . . . . . . . . . . . . . . . . . . . . . . . . . 209 Hunting in the trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Walking with directories . . . . . . . . . . . . . . . . . . . . . . . . . 211 Things to do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 Starting by sketching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 A magic manifests function . . . . . . . . . . . . . . . . . . . . . . . 212 Running Cargo and storing the output . . . . . . . . . . . . . . . . . . 213 Testing manifests . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 The stub of an idea . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 From underpromising to overdelivering . . . . . . . . . . . . . . . . . 215 Being more selective . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 Cleaning on command . . . . . . . . . . . . . . . . . . . . . . . . . . 216 Testing the command‐building function . . . . . . . . . . . . . . . . . 216 Catching a turbofish . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 Implementing cargo_clean_cmd . . . . . . . . . . . . . . . . . . . . . 218 Designing our output . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 The magic summary function . . . . . . . . . . . . . . . . . . . . . . . 219 Implementing summary . . . . . . . . . . . . . . . . . . . . . . . . . . 220 Parents of paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220 Massaging the figures . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 The final piece . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Slimming in action . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Running dry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Adding a --dry-run flag . . . . . . . . . . . . . . . . . . . . . . . . . 223 The magic struct type . . . . . . . . . . . . . . . . . . . . . . . . . . . 223 Testing dry‐run mode . . . . . . . . . . . . . . . . . . . . . . . . . . . 223 Adding a constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 Making a method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 Slim and slimmer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 9
Adding the flag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 One tool to slim them all . . . . . . . . . . . . . . . . . . . . . . . . . 228 A surprising result . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 The phantom project . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Reproducing the bug . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 What’s the problem? . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 Applying a cutoff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 A fickle, filtering file finder . . . . . . . . . . . . . . . . . . . . . . . . 231 Writing a Cargo plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 External subcommands . . . . . . . . . . . . . . . . . . . . . . . . . . 232 The subcommand argument . . . . . . . . . . . . . . . . . . . . . . . 232 Faking the binary name . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Parsing the arguments . . . . . . . . . . . . . . . . . . . . . . . . . . 233 The new main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 Naming the binary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Installing from a local crate . . . . . . . . . . . . . . . . . . . . . . . . 235 The secrets of software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 Eating the elephant one bite at a time . . . . . . . . . . . . . . . . . . . 236 The magic function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 From Rust, with love . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Until we meet again . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 About this book 238 Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 About me . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 Free updates to future editions . . . . . . . . . . . . . . . . . . . . . . . . . 239 Join my Code Club . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 Code For Your Life . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 For the Love of Go . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240 Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240 10
Praise for ‘The Secrets of Rust: Tools’ I’ve tried to learn Rust before, but bounced off it somehow. This book unlocked something for me, and now I have a better understanding of just what makes Rust so different. —Lawrence Denning I can’t praise this book enough. It’s opened my eyes to a whole new way of programming. —Jawahir Sheikh I really enjoy the project-based style—it helps to clarify how Rust’s features actually come to- gether into real programs. —Nick Chandler Gentle, funny, and full of clear explanations—one of the best introductory Rust books I’ve read. —Flavio Balioni It seems a little paid-by-the-word—the writer kind of goes around in circles, philosophizing and pontificating and asking rhetorical questions a bit too much. —Hacker News commenter JARED: Richard, adversity is a great teacher. Just like cigarette burns. —“Silicon Valley” 11
Introduction I have been writing a compiled, concurrent, safe, systems programming language for the past four and a half years. Spare-time kinda thing. Yeah, I got problems. —Graydon Hoare, Rust presentation, 2010 Hello, and welcome to the book! It’s great to have you here. Who is this book for? This book is aimed at those who have a little experience with Rust (or even a lot), and would now like to learn how to build good software with it. What is “good” software any‐ way? What would it look like in Rust? And how do we get there from here? There are lots of books that will teach you Rust, but not many that will show you what to do with it. In other words, once you’ve learned how to write Rust code, what code should you write? How do you bridge the yawning gap between toy programs for simpli‐ fied tutorials, and software that actually solves problems in the real world? 12
If software engineering is a craft, which it surely is, then how do we go about mastering it? It’s all very well to say “just write programs”, but how? How do we take some prob‐ lem and start designing a program to solve it? How can we incorporate tests into the design? What are we even aiming to do here? I hope you’ll find at least some useful answers to these questions in this book, which focuses on developing command-line tools, but most of it applies to any kind of Rust pro‐ gram. What should I know before reading it? It’s okay to read this book if you’re totally new to Rust, but it will really help you to read at least the first few chapters of the official introductory Rust book, “The Rust Program‐ ming Language”, beforehand. It’s free to read online. The first six chapters of the Rust book will give you a good grounding in some of the fundamental concepts we’ll be putting to work in The Secrets of Rust: Tools. And they’re so well‐written that there’s no point me trying to duplicate the same content here. Accordingly, I’ll assume you’ve read these chapters, but not necessarily that you’ve un‐ derstood and memorised them—that’s a bit much to ask! As we work through this book, I’ll introduce and explain each new Rust feature briefly, and you may find it helpful to refer back to the official Rust book from time to time, to get more background on some‐ thing, or more detailed explanations. As we progress, you can return to the Rust book and read further chapters, building on what you’ve learned. If it’s all a bit overwhelming, don’t worry: it will make sense eventually. Rust isn’t difficult: it’s just different. I hope this book will help things slot into place for you. How can I play along at home? Throughout this book, we develop a number of Rust programs, step by step. Each chap‐ ter includes a number of code challenges so that you can take part in the development yourself, if you want to. You’re welcome to skip these, or come back to them, but you’ll probably find it helps to at least attempt some of the challenges as you’re reading. Each challenge sets you a goal to achieve, marked with the word GOAL, like this: GOAL: Get the test passing. When you see this, stop reading at that point and see if you can figure out how to solve the problem. You can try to write the code and check it against the tests, or just think about what you would do. If you reckon you have the answer, or alternatively if you’ve got a bit stuck and aren’t 13
sure what to do, you can then read on for a HINT, or read on even further for step‐by‐ step instructions on how to construct the SOLUTION. If you run into trouble, or just want to check your code, each challenge is accompanied by a complete sample solution, with tests. In effect, you can play the book on three difficulty levels: • Easy: just follow along as we develop the solutions to each challenge; they’re ex‐ plained line by line and step by step, so even if you have practically no Rust expe‐ rience, you should still be able to follow everything just fine. • Medium: you can attempt each challenge yourself, but get hints and guidance on the right way to solve the problem, before reading on to see the suggested solu‐ tion. • Hard: tackle the challenges without reading the hints, and use your own initia‐ tive to figure out what to do. You can still look at the hints if you get stuck, but the more you can do on your own, the more it’ll build your confidence as a Rust pro‐ grammer. How do I install Rust? While Rust may be available as a package in your operating system distribution, the recommended and best way to install it is via the rustup tool: • https://www.rust‐lang.org/tools/install Once you have rustup on your system, you can use it to update to the latest Rust ver‐ sions in future, as well as the standard Rust tools such as cargo and clippy. What editor or IDE should I use? Whichever you prefer. Visual Studio Code, Zed, Vim, and most other popular editors feature full support for Rust, and will work just fine for the projects in this book. Where do I find the listings and example solutions? All the code listings in the book, including the challenge solutions, are hyperlinked within the text, and they’re also available in a public GitHub repo here: • https://github.com/bitfield/tsr‐tools Each listing in the book is accompanied by a name and number (for example, listing hello_1), and you’ll find the solution to that exercise in the corresponding folder of the repo. 14
What you’ll learn By reading through this book and completing the exercises, you’ll learn: • How to build reusable crates instead of one‐off programs • How to design user‐friendly APIs and packages, without annoying paperwork • How to write robust, testable tools that take command‐line flags and arguments • How to detect, manage, and present run‐time errors • How to design Rust packages that work with files and other kinds of binary data • How to work with persistent data and serialization to and from JSON • How to create robust, reusable client packages for HTTP services and other APIs • How to launch external commands and capture their output, how to use the clap crate to parse arguments and subcommands, and how to add new features to the Cargo tool • How to write useful, informative, and high‐quality automated unit tests and inte‐ gration tests 15
1. Crates When it was announced that the Library contained all books, the first reaction was unbounded joy. All… felt themselves the possessors of an intact and secret treasure. There was no personal problem, no world problem, whose eloquent solu- tion did not exist—somewhere… —Jorge Luis Borges, “The Library of Babel” Hello, world! What’s wrong with this program? fn main() { println!("Hello, world!"); } (Listing hello_1) If you looked in vain, unable to spot the bug, I don’t blame you. The fact is, there’s noth‐ ing wrong with this program as such. It works, to the extent that it does what the author intended: it prints a message to the terminal, and exits. 16
You’ve probably seen it before; it’s the default program that Cargo, the Rust build tool, creates for you when you run cargo new to create a new Rust project. But there are some limitations on what we can do with this program. The most serious of these is that it’s not importable: it’s not a library crate, which is Rust’s name for packages of code that people can download and use in their programs. So let’s fix that. We’ll take this simple program that prints “Hello, world!” and turn it into a crate that we can use to build “Hello, world!” programs—including this one. Hello as a service First, we’ll use Cargo to create a new Rust project named hello: cargo new hello Creating binary (application) `hello` package We’ll be doing a few things in here, so let’s make it our working directory: cd hello As expected, the src/main.rs file looks exactly like listing hello_1: fn main() { println!("Hello, world!"); } We’ll keep this binary crate around, so that we have some command we can run and see the results. But we’d like to move the substantive functionality (that is, printing a hello message) into a library crate, which we can then call from the main function. The simplest thing imaginable Cargo expects a library crate to be in a file named src/lib.rs, just as a binary crate is expected to live at src/main.rs. So we’ll create the lib.rs file and add the function to it. Let’s start by doing the simplest thing we can imagine: just writing some function that does what our existing main function does, which is to print “Hello, world!”. pub fn print() { println!("Hello, world!"); } (Listing hello_2) Library functions are private by default, so to make something part of our public API we need to tag it as such using the pub keyword. This means we can call it from outside the crate, which we can now do from our main function: 17
use hello::print; fn main() { print(); } (Listing hello_2) First, the use declaration tells Rust that we intend to call the print function from the hello library crate, and means that we can later use just the name print in our code without further qualification. And that’s it! Let’s run the binary and check that every‐ thing still works: cargo run Hello, world! Reassuring. So, if we were to publish this crate now (by uploading it to crates.io, for example), the wider Rust community could enjoy the fruits of our labours. Any time they need to print a “Hello, world!” message, they can import our library crate and call its print function. Great! Raiders of the lost crate The Rust language is nice, and the standard library isn’t bad, but it’s pretty limited, by design. The real killer feature of Rust is this amazing collection of user‐contributed crates, or reusable components. We can use them to build just about any program imaginable, just by gluing together the right crates. We might call this wider Rust software ecosystem, slightly whimsically, the universal library. And it’s big. Really big. crates.io currently lists over 160,000 crates. Chances are, whatever you want to do, you can find an existing crate that will at least give you a head start. Indeed, sometimes the universal library of Rust can seem a bit like the giant warehouse in Raiders of the Lost Ark where the government puts things it doesn’t want anyone to find, as seen at the head of this chapter. There are just so many Rust crates, indeed, that even though you know the thing you want must exist somewhere, it can be hard to actually locate it. We’ll learn about some of the best and most widely‐used crates in this book, but it’s impossible to cover more than a very few in detail. blessed.rs is a useful curated guide to the “de facto” standard library, if you like, and it’s a good place to start looking for the crate you want. 18
Testing One of the other things that makes Rust so great is its emphasis on correctness and re‐ liability. That comes partly from the design of the language itself, but it’s also some‐ thing its users care a lot about. People choose Rust because they want to build durable, dependable software, so how does that happen in practice? Testing is a big part of it. Automated tests give us confidence that the code we’re writing is correct, not just today, but in ten years time. Writing tests like this also has the bene‐ fit of clarifying and making the requirements explicit. If you can’t tell a computer how to check something, it’s usually because you don’t really understand it yourself, and that’s the first problem you need to solve. Rust’s built‐in test framework Rust doesn’t need any special framework, library, or extra tooling to enable testing: it’s built right into the Cargo tool. And a test itself is nothing special either: it’s just a plain old Rust function. For example, let’s add a very simple test function to our existing src/lib.rs file. All we need to do to make it a “test” is to add the #[test] attribute above the function: #[test] fn always_passes() {} Wow, that was easier than I expected! The function doesn’t even have to do anything. Let’s see what happens when we tell Cargo to run our test suite: cargo test running 1 test test always_passes ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s And there’s more output, but it’s not of interest at the moment. There are three things worth noting in this output: • We can see howmany tests are being run (“running 1 test”) • We see the name and result of each test (“always_passes … ok”) • We see a summary of all test results (“1 passed; 0 failed…”) Failing tests A test that always passes isn’t much use, of course, so how would we make a test fail, if we needed to? The most common way to do that, though it might seem a little brusque, is to make the test function panic: 19
#[test] fn always_fails() { panic!("oh no"); } Now the output from cargo test will look like this: running 2 tests test always_passes ... ok test always_fails ... FAILED failures: ---- always_fails stdout ---- thread 'always_fails' panicked at hello/src/lib.rs:10:5: oh no ... failures: always_fails test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s The always_passes test still passes, not surprisingly, but our new test doesn’t: test always_fails ... FAILED There could be lots of reasons for a test to fail, so this output includes the actual mes‐ sage we passed to panic!: ---- always_fails stdout ---- thread 'always_fails' panicked at hello/src/lib.rs:10:5: oh no We get the filename, line number, and even the column position of the panic! call, just in case that helps us track down the failure, and we also see the panic message itself: “oh no”. A test that always fails is not much more use than a test that always passes, I admit, but it is at least slightly more educational. Now we know how to make a test fail when it needs to, and usually that will be because the function we’re testing did something un‐ expected. 20