]> git.proxmox.com Git - rustc.git/blame - src/doc/book/src/ch12-03-improving-error-handling-and-modularity.md
New upstream version 1.79.0+dfsg1
[rustc.git] / src / doc / book / src / ch12-03-improving-error-handling-and-modularity.md
CommitLineData
13cf67c4
XL
1## Refactoring to Improve Modularity and Error Handling
2
3To improve our program, we’ll fix four problems that have to do with the
04454e1e
FG
4program’s structure and how it’s handling potential errors. First, our `main`
5function now performs two tasks: it parses arguments and reads files. As our
6program grows, the number of separate tasks the `main` function handles will
7increase. As a function gains responsibilities, it becomes more difficult to
8reason about, harder to test, and harder to change without breaking one of its
9parts. It’s best to separate functionality so each function is responsible for
10one task.
13cf67c4 11
923072b8 12This issue also ties into the second problem: although `query` and `file_path`
13cf67c4
XL
13are configuration variables to our program, variables like `contents` are used
14to perform the program’s logic. The longer `main` becomes, the more variables
15we’ll need to bring into scope; the more variables we have in scope, the harder
16it will be to keep track of the purpose of each. It’s best to group the
17configuration variables into one structure to make their purpose clear.
18
19The third problem is that we’ve used `expect` to print an error message when
923072b8
FG
20reading the file fails, but the error message just prints `Should have been
21able to read the file`. Reading a file can fail in a number of ways: for
22example, the file could be missing, or we might not have permission to open it.
23Right now, regardless of the situation, we’d print the same error message for
04454e1e 24everything, which wouldn’t give the user any information!
13cf67c4 25
e8be2606
FG
26Fourth, we use `expect` to handle an error, and if the user runs our program
27without specifying enough arguments, they’ll get an `index out of bounds` error
28from Rust that doesn’t clearly explain the problem. It would be best if all the
29error-handling code were in one place so future maintainers had only one place
30to consult the code if the error-handling logic needed to change. Having all the
31error-handling code in one place will also ensure that we’re printing messages
32that will be meaningful to our end users.
13cf67c4
XL
33
34Let’s address these four problems by refactoring our project.
35
36### Separation of Concerns for Binary Projects
37
38The organizational problem of allocating responsibility for multiple tasks to
39the `main` function is common to many binary projects. As a result, the Rust
04454e1e
FG
40community has developed guidelines for splitting the separate concerns of a
41binary program when `main` starts getting large. This process has the following
42steps:
13cf67c4
XL
43
44* Split your program into a *main.rs* and a *lib.rs* and move your program’s
45 logic to *lib.rs*.
46* As long as your command line parsing logic is small, it can remain in
47 *main.rs*.
48* When the command line parsing logic starts getting complicated, extract it
49 from *main.rs* and move it to *lib.rs*.
13cf67c4 50
9fa01778
XL
51The responsibilities that remain in the `main` function after this process
52should be limited to the following:
53
54* Calling the command line parsing logic with the argument values
55* Setting up any other configuration
56* Calling a `run` function in *lib.rs*
57* Handling the error if `run` returns an error
13cf67c4
XL
58
59This pattern is about separating concerns: *main.rs* handles running the
60program, and *lib.rs* handles all the logic of the task at hand. Because you
61can’t test the `main` function directly, this structure lets you test all of
04454e1e
FG
62your program’s logic by moving it into functions in *lib.rs*. The code that
63remains in *main.rs* will be small enough to verify its correctness by reading
64it. Let’s rework our program by following this process.
13cf67c4
XL
65
66#### Extracting the Argument Parser
67
68We’ll extract the functionality for parsing arguments into a function that
69`main` will call to prepare for moving the command line parsing logic to
70*src/lib.rs*. Listing 12-5 shows the new start of `main` that calls a new
71function `parse_config`, which we’ll define in *src/main.rs* for the moment.
72
73<span class="filename">Filename: src/main.rs</span>
74
75```rust,ignore
74b04a01 76{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-05/src/main.rs:here}}
13cf67c4
XL
77```
78
79<span class="caption">Listing 12-5: Extracting a `parse_config` function from
80`main`</span>
81
82We’re still collecting the command line arguments into a vector, but instead of
9fa01778 83assigning the argument value at index 1 to the variable `query` and the
923072b8 84argument value at index 2 to the variable `file_path` within the `main`
13cf67c4
XL
85function, we pass the whole vector to the `parse_config` function. The
86`parse_config` function then holds the logic that determines which argument
87goes in which variable and passes the values back to `main`. We still create
923072b8 88the `query` and `file_path` variables in `main`, but `main` no longer has the
13cf67c4
XL
89responsibility of determining how the command line arguments and variables
90correspond.
91
92This rework may seem like overkill for our small program, but we’re refactoring
93in small, incremental steps. After making this change, run the program again to
94verify that the argument parsing still works. It’s good to check your progress
95often, to help identify the cause of problems when they occur.
96
97#### Grouping Configuration Values
98
99We can take another small step to improve the `parse_config` function further.
100At the moment, we’re returning a tuple, but then we immediately break that
101tuple into individual parts again. This is a sign that perhaps we don’t have
102the right abstraction yet.
103
104Another indicator that shows there’s room for improvement is the `config` part
105of `parse_config`, which implies that the two values we return are related and
106are both part of one configuration value. We’re not currently conveying this
107meaning in the structure of the data other than by grouping the two values into
04454e1e 108a tuple; we’ll instead put the two values into one struct and give each of the
13cf67c4
XL
109struct fields a meaningful name. Doing so will make it easier for future
110maintainers of this code to understand how the different values relate to each
111other and what their purpose is.
112
9fa01778 113Listing 12-6 shows the improvements to the `parse_config` function.
13cf67c4
XL
114
115<span class="filename">Filename: src/main.rs</span>
116
6a06907d 117```rust,should_panic,noplayground
74b04a01 118{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-06/src/main.rs:here}}
13cf67c4
XL
119```
120
121<span class="caption">Listing 12-6: Refactoring `parse_config` to return an
122instance of a `Config` struct</span>
123
9fa01778 124We’ve added a struct named `Config` defined to have fields named `query` and
923072b8 125`file_path`. The signature of `parse_config` now indicates that it returns a
04454e1e
FG
126`Config` value. In the body of `parse_config`, where we used to return
127string slices that reference `String` values in `args`, we now define `Config`
128to contain owned `String` values. The `args` variable in `main` is the owner of
9fa01778
XL
129the argument values and is only letting the `parse_config` function borrow
130them, which means we’d violate Rust’s borrowing rules if `Config` tried to take
131ownership of the values in `args`.
13cf67c4 132
04454e1e
FG
133There are a number of ways we could manage the `String` data; the easiest,
134though somewhat inefficient, route is to call the `clone` method on the values.
135This will make a full copy of the data for the `Config` instance to own, which
136takes more time and memory than storing a reference to the string data.
137However, cloning the data also makes our code very straightforward because we
138don’t have to manage the lifetimes of the references; in this circumstance,
139giving up a little performance to gain simplicity is a worthwhile trade-off.
13cf67c4
XL
140
141> ### The Trade-Offs of Using `clone`
142>
143> There’s a tendency among many Rustaceans to avoid using `clone` to fix
dc9dc135
XL
144> ownership problems because of its runtime cost. In
145> [Chapter 13][ch13]<!-- ignore -->, you’ll learn how to use more efficient
146> methods in this type of situation. But for now, it’s okay to copy a few
147> strings to continue making progress because you’ll make these copies only
923072b8 148> once and your file path and query string are very small. It’s better to have
dc9dc135
XL
149> a working program that’s a bit inefficient than to try to hyperoptimize code
150> on your first pass. As you become more experienced with Rust, it’ll be
151> easier to start with the most efficient solution, but for now, it’s
152> perfectly acceptable to call `clone`.
13cf67c4
XL
153
154We’ve updated `main` so it places the instance of `Config` returned by
155`parse_config` into a variable named `config`, and we updated the code that
923072b8 156previously used the separate `query` and `file_path` variables so it now uses
13cf67c4
XL
157the fields on the `Config` struct instead.
158
923072b8 159Now our code more clearly conveys that `query` and `file_path` are related and
13cf67c4
XL
160that their purpose is to configure how the program will work. Any code that
161uses these values knows to find them in the `config` instance in the fields
162named for their purpose.
163
164#### Creating a Constructor for `Config`
165
166So far, we’ve extracted the logic responsible for parsing the command line
167arguments from `main` and placed it in the `parse_config` function. Doing so
923072b8 168helped us to see that the `query` and `file_path` values were related and that
13cf67c4 169relationship should be conveyed in our code. We then added a `Config` struct to
923072b8 170name the related purpose of `query` and `file_path` and to be able to return the
13cf67c4
XL
171values’ names as struct field names from the `parse_config` function.
172
173So now that the purpose of the `parse_config` function is to create a `Config`
174instance, we can change `parse_config` from a plain function to a function
175named `new` that is associated with the `Config` struct. Making this change
176will make the code more idiomatic. We can create instances of types in the
177standard library, such as `String`, by calling `String::new`. Similarly, by
178changing `parse_config` into a `new` function associated with `Config`, we’ll
179be able to create instances of `Config` by calling `Config::new`. Listing 12-7
9fa01778 180shows the changes we need to make.
13cf67c4
XL
181
182<span class="filename">Filename: src/main.rs</span>
183
6a06907d 184```rust,should_panic,noplayground
74b04a01 185{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-07/src/main.rs:here}}
13cf67c4
XL
186```
187
188<span class="caption">Listing 12-7: Changing `parse_config` into
189`Config::new`</span>
190
191We’ve updated `main` where we were calling `parse_config` to instead call
192`Config::new`. We’ve changed the name of `parse_config` to `new` and moved it
193within an `impl` block, which associates the `new` function with `Config`. Try
194compiling this code again to make sure it works.
195
196### Fixing the Error Handling
197
198Now we’ll work on fixing our error handling. Recall that attempting to access
9fa01778
XL
199the values in the `args` vector at index 1 or index 2 will cause the program to
200panic if the vector contains fewer than three items. Try running the program
201without any arguments; it will look like this:
13cf67c4 202
f035d41b 203```console
74b04a01 204{{#include ../listings/ch12-an-io-project/listing-12-07/output.txt}}
13cf67c4
XL
205```
206
207The line `index out of bounds: the len is 1 but the index is 1` is an error
208message intended for programmers. It won’t help our end users understand what
04454e1e 209they should do instead. Let’s fix that now.
13cf67c4
XL
210
211#### Improving the Error Message
212
213In Listing 12-8, we add a check in the `new` function that will verify that the
9fa01778 214slice is long enough before accessing index 1 and 2. If the slice isn’t long
04454e1e 215enough, the program panics and displays a better error message.
13cf67c4
XL
216
217<span class="filename">Filename: src/main.rs</span>
218
219```rust,ignore
74b04a01 220{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-08/src/main.rs:here}}
13cf67c4
XL
221```
222
223<span class="caption">Listing 12-8: Adding a check for the number of
224arguments</span>
225
48663c56 226This code is similar to [the `Guess::new` function we wrote in Listing
5099ac24 2279-13][ch9-custom-types]<!-- ignore -->, where we called `panic!` when the
48663c56
XL
228`value` argument was out of the range of valid values. Instead of checking for
229a range of values here, we’re checking that the length of `args` is at least 3
230and the rest of the function can operate under the assumption that this
231condition has been met. If `args` has fewer than three items, this condition
232will be true, and we call the `panic!` macro to end the program immediately.
13cf67c4
XL
233
234With these extra few lines of code in `new`, let’s run the program without any
235arguments again to see what the error looks like now:
236
f035d41b 237```console
74b04a01 238{{#include ../listings/ch12-an-io-project/listing-12-08/output.txt}}
13cf67c4
XL
239```
240
241This output is better: we now have a reasonable error message. However, we also
242have extraneous information we don’t want to give to our users. Perhaps using
3c0e092e 243the technique we used in Listing 9-13 isn’t the best to use here: a call to
48663c56 244`panic!` is more appropriate for a programming problem than a usage problem,
04454e1e
FG
245[as discussed in Chapter 9][ch9-error-guidelines]<!-- ignore -->. Instead,
246we’ll use the other technique you learned about in Chapter 9—[returning a
48663c56 247`Result`][ch9-result]<!-- ignore --> that indicates either success or an error.
13cf67c4 248
923072b8
FG
249<!-- Old headings. Do not remove or links may break. -->
250<a id="returning-a-result-from-new-instead-of-calling-panic"></a>
13cf67c4 251
923072b8 252#### Returning a `Result` Instead of Calling `panic!`
13cf67c4 253
923072b8
FG
254We can instead return a `Result` value that will contain a `Config` instance in
255the successful case and will describe the problem in the error case. We’re also
256going to change the function name from `new` to `build` because many
257programmers expect `new` functions to never fail. When `Config::build` is
258communicating to `main`, we can use the `Result` type to signal there was a
259problem. Then we can change `main` to convert an `Err` variant into a more
260practical error for our users without the surrounding text about `thread
261'main'` and `RUST_BACKTRACE` that a call to `panic!` causes.
262
263Listing 12-9 shows the changes we need to make to the return value of the
264function we’re now calling `Config::build` and the body of the function needed
265to return a `Result`. Note that this won’t compile until we update `main` as
266well, which we’ll do in the next listing.
13cf67c4
XL
267
268<span class="filename">Filename: src/main.rs</span>
269
a2a8927a 270```rust,ignore,does_not_compile
74b04a01 271{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-09/src/main.rs:here}}
13cf67c4
XL
272```
273
274<span class="caption">Listing 12-9: Returning a `Result` from
923072b8 275`Config::build`</span>
13cf67c4 276
064997fb
FG
277Our `build` function returns a `Result` with a `Config` instance in the success
278case and a `&'static str` in the error case. Our error values will always be
279string literals that have the `'static` lifetime.
13cf67c4 280
923072b8
FG
281We’ve made two changes in the body of the function: instead of calling `panic!`
282when the user doesn’t pass enough arguments, we now return an `Err` value, and
283we’ve wrapped the `Config` return value in an `Ok`. These changes make the
284function conform to its new type signature.
285
286Returning an `Err` value from `Config::build` allows the `main` function to
287handle the `Result` value returned from the `build` function and exit the
288process more cleanly in the error case.
13cf67c4 289
923072b8
FG
290<!-- Old headings. Do not remove or links may break. -->
291<a id="calling-confignew-and-handling-errors"></a>
13cf67c4 292
923072b8 293#### Calling `Config::build` and Handling Errors
13cf67c4
XL
294
295To handle the error case and print a user-friendly message, we need to update
923072b8 296`main` to handle the `Result` being returned by `Config::build`, as shown in
13cf67c4 297Listing 12-10. We’ll also take the responsibility of exiting the command line
04454e1e
FG
298tool with a nonzero error code away from `panic!` and instead implement it by
299hand. A nonzero exit status is a convention to signal to the process that
300called our program that the program exited with an error state.
13cf67c4
XL
301
302<span class="filename">Filename: src/main.rs</span>
303
304```rust,ignore
74b04a01 305{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-10/src/main.rs:here}}
13cf67c4
XL
306```
307
923072b8
FG
308<span class="caption">Listing 12-10: Exiting with an error code if building a
309`Config` fails</span>
13cf67c4 310
6a06907d 311In this listing, we’ve used a method we haven’t covered in detail yet:
13cf67c4
XL
312`unwrap_or_else`, which is defined on `Result<T, E>` by the standard library.
313Using `unwrap_or_else` allows us to define some custom, non-`panic!` error
314handling. If the `Result` is an `Ok` value, this method’s behavior is similar
315to `unwrap`: it returns the inner value `Ok` is wrapping. However, if the value
316is an `Err` value, this method calls the code in the *closure*, which is an
317anonymous function we define and pass as an argument to `unwrap_or_else`. We’ll
48663c56
XL
318cover closures in more detail in [Chapter 13][ch13]<!-- ignore -->. For now,
319you just need to know that `unwrap_or_else` will pass the inner value of the
fc512014 320`Err`, which in this case is the static string `"not enough arguments"` that we
48663c56
XL
321added in Listing 12-9, to our closure in the argument `err` that appears
322between the vertical pipes. The code in the closure can then use the `err`
323value when it runs.
13cf67c4
XL
324
325We’ve added a new `use` line to bring `process` from the standard library into
326scope. The code in the closure that will be run in the error case is only two
327lines: we print the `err` value and then call `process::exit`. The
328`process::exit` function will stop the program immediately and return the
329number that was passed as the exit status code. This is similar to the
330`panic!`-based handling we used in Listing 12-8, but we no longer get all the
331extra output. Let’s try it:
332
f035d41b 333```console
74b04a01 334{{#include ../listings/ch12-an-io-project/listing-12-10/output.txt}}
13cf67c4
XL
335```
336
337Great! This output is much friendlier for our users.
338
339### Extracting Logic from `main`
340
341Now that we’ve finished refactoring the configuration parsing, let’s turn to
9fa01778
XL
342the program’s logic. As we stated in [“Separation of Concerns for Binary
343Projects”](#separation-of-concerns-for-binary-projects)<!-- ignore -->, we’ll
344extract a function named `run` that will hold all the logic currently in the
345`main` function that isn’t involved with setting up configuration or handling
346errors. When we’re done, `main` will be concise and easy to verify by
347inspection, and we’ll be able to write tests for all the other logic.
13cf67c4
XL
348
349Listing 12-11 shows the extracted `run` function. For now, we’re just making
350the small, incremental improvement of extracting the function. We’re still
351defining the function in *src/main.rs*.
352
353<span class="filename">Filename: src/main.rs</span>
354
355```rust,ignore
74b04a01 356{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-11/src/main.rs:here}}
13cf67c4
XL
357```
358
359<span class="caption">Listing 12-11: Extracting a `run` function containing the
360rest of the program logic</span>
361
362The `run` function now contains all the remaining logic from `main`, starting
363from reading the file. The `run` function takes the `Config` instance as an
364argument.
365
366#### Returning Errors from the `run` Function
367
368With the remaining program logic separated into the `run` function, we can
923072b8 369improve the error handling, as we did with `Config::build` in Listing 12-9.
13cf67c4
XL
370Instead of allowing the program to panic by calling `expect`, the `run`
371function will return a `Result<T, E>` when something goes wrong. This will let
04454e1e 372us further consolidate the logic around handling errors into `main` in a
13cf67c4 373user-friendly way. Listing 12-12 shows the changes we need to make to the
9fa01778 374signature and body of `run`.
13cf67c4
XL
375
376<span class="filename">Filename: src/main.rs</span>
377
378```rust,ignore
74b04a01 379{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-12/src/main.rs:here}}
13cf67c4
XL
380```
381
382<span class="caption">Listing 12-12: Changing the `run` function to return
383`Result`</span>
384
385We’ve made three significant changes here. First, we changed the return type of
386the `run` function to `Result<(), Box<dyn Error>>`. This function previously
387returned the unit type, `()`, and we keep that as the value returned in the
388`Ok` case.
389
390For the error type, we used the *trait object* `Box<dyn Error>` (and we’ve
391brought `std::error::Error` into scope with a `use` statement at the top).
48663c56
XL
392We’ll cover trait objects in [Chapter 17][ch17]<!-- ignore -->. For now, just
393know that `Box<dyn Error>` means the function will return a type that
394implements the `Error` trait, but we don’t have to specify what particular type
395the return value will be. This gives us flexibility to return error values that
396may be of different types in different error cases. The `dyn` keyword is short
397for “dynamic.”
13cf67c4 398
9fa01778 399Second, we’ve removed the call to `expect` in favor of the `?` operator, as we
48663c56
XL
400talked about in [Chapter 9][ch9-question-mark]<!-- ignore -->. Rather than
401`panic!` on an error, `?` will return the error value from the current function
402for the caller to handle.
13cf67c4 403
04454e1e
FG
404Third, the `run` function now returns an `Ok` value in the success case.
405We’ve declared the `run` function’s success type as `()` in the signature,
406which means we need to wrap the unit type value in the `Ok` value. This
407`Ok(())` syntax might look a bit strange at first, but using `()` like this is
408the idiomatic way to indicate that we’re calling `run` for its side effects
409only; it doesn’t return a value we need.
13cf67c4
XL
410
411When you run this code, it will compile but will display a warning:
412
f035d41b 413```console
74b04a01 414{{#include ../listings/ch12-an-io-project/listing-12-12/output.txt}}
13cf67c4
XL
415```
416
417Rust tells us that our code ignored the `Result` value and the `Result` value
418might indicate that an error occurred. But we’re not checking to see whether or
419not there was an error, and the compiler reminds us that we probably meant to
9fa01778 420have some error-handling code here! Let’s rectify that problem now.
13cf67c4
XL
421
422#### Handling Errors Returned from `run` in `main`
423
424We’ll check for errors and handle them using a technique similar to one we used
923072b8 425with `Config::build` in Listing 12-10, but with a slight difference:
13cf67c4
XL
426
427<span class="filename">Filename: src/main.rs</span>
428
429```rust,ignore
74b04a01 430{{#rustdoc_include ../listings/ch12-an-io-project/no-listing-01-handling-errors-in-main/src/main.rs:here}}
13cf67c4
XL
431```
432
433We use `if let` rather than `unwrap_or_else` to check whether `run` returns an
434`Err` value and call `process::exit(1)` if it does. The `run` function doesn’t
923072b8 435return a value that we want to `unwrap` in the same way that `Config::build`
13cf67c4
XL
436returns the `Config` instance. Because `run` returns `()` in the success case,
437we only care about detecting an error, so we don’t need `unwrap_or_else` to
04454e1e 438return the unwrapped value, which would only be `()`.
13cf67c4
XL
439
440The bodies of the `if let` and the `unwrap_or_else` functions are the same in
441both cases: we print the error and exit.
442
443### Splitting Code into a Library Crate
444
445Our `minigrep` project is looking good so far! Now we’ll split the
04454e1e
FG
446*src/main.rs* file and put some code into the *src/lib.rs* file. That way we
447can test the code and have a *src/main.rs* file with fewer responsibilities.
13cf67c4
XL
448
449Let’s move all the code that isn’t the `main` function from *src/main.rs* to
450*src/lib.rs*:
451
452* The `run` function definition
453* The relevant `use` statements
454* The definition of `Config`
923072b8 455* The `Config::build` function definition
13cf67c4
XL
456
457The contents of *src/lib.rs* should have the signatures shown in Listing 12-13
458(we’ve omitted the bodies of the functions for brevity). Note that this won’t
9fa01778 459compile until we modify *src/main.rs* in Listing 12-14.
13cf67c4
XL
460
461<span class="filename">Filename: src/lib.rs</span>
462
a2a8927a 463```rust,ignore,does_not_compile
74b04a01 464{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-13/src/lib.rs:here}}
13cf67c4
XL
465```
466
467<span class="caption">Listing 12-13: Moving `Config` and `run` into
468*src/lib.rs*</span>
469
470We’ve made liberal use of the `pub` keyword: on `Config`, on its fields and its
064997fb
FG
471`build` method, and on the `run` function. We now have a library crate that has
472a public API we can test!
13cf67c4
XL
473
474Now we need to bring the code we moved to *src/lib.rs* into the scope of the
9fa01778 475binary crate in *src/main.rs*, as shown in Listing 12-14.
13cf67c4
XL
476
477<span class="filename">Filename: src/main.rs</span>
478
479```rust,ignore
74b04a01 480{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-14/src/main.rs:here}}
13cf67c4
XL
481```
482
9fa01778
XL
483<span class="caption">Listing 12-14: Using the `minigrep` library crate in
484*src/main.rs*</span>
13cf67c4 485
9fa01778 486We add a `use minigrep::Config` line to bring the `Config` type from the
532ac7d7 487library crate into the binary crate’s scope, and we prefix the `run` function
9fa01778
XL
488with our crate name. Now all the functionality should be connected and should
489work. Run the program with `cargo run` and make sure everything works
490correctly.
13cf67c4
XL
491
492Whew! That was a lot of work, but we’ve set ourselves up for success in the
493future. Now it’s much easier to handle errors, and we’ve made the code more
494modular. Almost all of our work will be done in *src/lib.rs* from here on out.
495
496Let’s take advantage of this newfound modularity by doing something that would
497have been difficult with the old code but is easy with the new code: we’ll
498write some tests!
9fa01778 499
48663c56
XL
500[ch13]: ch13-00-functional-features.html
501[ch9-custom-types]: ch09-03-to-panic-or-not-to-panic.html#creating-custom-types-for-validation
502[ch9-error-guidelines]: ch09-03-to-panic-or-not-to-panic.html#guidelines-for-error-handling
503[ch9-result]: ch09-02-recoverable-errors-with-result.html
504[ch17]: ch17-00-oop.html
505[ch9-question-mark]: ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator