diff --git a/.travis.yml b/.travis.yml index dd89cceeb0..9d81f13e36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,10 @@ language: rust cache: cargo rust: - nightly - - stable +# - beta +# ^ to be changed once question_mark is in beta: 1.14 +# - stable +# ^ to be changed once question_mark becomes stable: 1.14 before_script: - (cargo install mdbook || true) script: diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 8e4ad4a8b1..c7faee8170 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -36,7 +36,10 @@ - [Strings]() - [`HashMap`]() -- [Error Handling]() +- [Error Handling](ch09-00-error-handling.md) + - [Unrecoverable Errors with `panic!`](ch09-01-unrecoverable-errors-with-panic.md) + - [Recoverable Errors with `Result`](ch09-02-recoverable-errors-with-result.md) + - [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md) - [Lifetimes]() diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md new file mode 100644 index 0000000000..9f4a1df647 --- /dev/null +++ b/src/ch09-00-error-handling.md @@ -0,0 +1,26 @@ +# Error Handling + +Rust's focus on reliability extends to the area of error handling. Errors are a +fact of life in software, so Rust has a number of features that you can use to +handle situations in which something bad happens. In many cases, Rust requires +you to acknowledge the possibility of an error occurring and take some action +in that situation. This makes your program more robust by eliminating the +possibility of unexpected errors only being discovered after you've deployed +your code to production. + +Rust groups errors into two major kinds: errors that are *recoverable*, and +errors that are *unrecoverable*. Recoverable errors are problems like a file not +being found, where it's usually reasonable to report that problem to the user +and retry the operation. Unrecoverable errors are problems like trying to +access a location beyond the end of an array, and these are always symptoms of +bugs. + +Most languages do not distinguish between the two kinds of errors, so they +handle both kinds in the same way using mechanisms like exceptions. Rust +doesn't have exceptions. Instead, it has the value `Result` to return in +the case of recoverable errors and the `panic!` macro that stops execution when +it encounters unrecoverable errors. This chapter will cover the more +straightforward case of calling `panic!` first. Then, we'll talk about +returning `Result` values and calling functions that return `Result`. Finally, we'll discuss considerations to take into account when deciding +whether to try to recover from an error or to stop execution. diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md new file mode 100644 index 0000000000..74f93a5f0d --- /dev/null +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -0,0 +1,145 @@ +## Unrecoverable Errors with `panic!` + +Sometimes, bad things happen, and there's nothing that you can do about it. For +these cases, Rust has a macro, `panic!`. When this macro executes, your program +will print a failure message, unwind and clean up the stack, and then quit. The +most common reason for this is when a bug of some kind has been detected, and +it's not clear how to handle the error. + + + +> #### Unwinding +> By default, when a `panic!` happens in Rust, the program starts +> *unwinding*, which means Rust walks back up the stack and cleans up the data +> from each function it encounters. Doing that walking and cleanup is a lot of +> work. The alternative is to immediately `abort`, which ends the program +> without cleaning up. Memory that the program was using will need to be cleaned +> up by the operating system. If you're in a situation where you need to make +> the resulting binary as small as possible, you can switch from unwinding on +> panic to aborting on panic by adding `panic = 'abort'` to the appropriate +> `[profile]` sections in your `Cargo.toml`. + + + +Let's try out calling `panic!()` with a simple program: + +```rust,should_panic +fn main() { + panic!("crash and burn"); +} +``` + +If you run it, you'll see something like this: + +```bash +$ cargo run + Compiling panic v0.1.0 (file:///projects/panic) + Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs + Running `target/debug/panic` +thread 'main' panicked at 'crash and burn', src/main.rs:2 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) +``` + +There are three lines of error message here. The first line shows our panic +message and the place in our source code where the panic occurred: +`src/main.rs`, line two. + +But that only shows us the exact line that called `panic!`. That's not always +useful. Let's look at another example to see what it's like when a `panic!` +call comes from code we call instead of from our code directly: + +```rust,should_panic +fn main() { + let v = vec![1, 2, 3]; + + v[100]; +} +``` + +We're attempting to access the hundredth element of our vector, but it only has +three elements. In this situation, Rust will panic. Using `[]` is supposed to +return an element. If you pass `[]` an invalid index, though, there's no +element that Rust could return here that would be correct. + +Other languages like C will attempt to give you exactly what you asked for in +this situation, even though it isn't what you want: you'll get whatever is at +the location in memory that would correspond to that element in the vector, +even though the memory doesn't belong to the vector. This is called a *buffer +overread*, and can lead to security vulnerabilities if an attacker can +manipulate the index in such a way as to read data they shouldn't be allowed to +that is stored after the array. + +In order to protect your program from this sort of vulnerability, if you try to +read an element at an index that doesn't exist, Rust will stop execution and +refuse to continue with an invalid value. Let's try it and see: + +```bash +$ cargo run + Compiling panic v0.1.0 (file:///projects/panic) + Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs + Running `target/debug/panic` +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is +100', ../src/libcollections/vec.rs:1265 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) +``` + +This points at a file we didn't write, `../src/libcollections/vec.rs`. That's +the implementation of `Vec` in the standard library. While it's easy to see +in this short program where the error was, it would be nicer if we could have +Rust tell us what line in our program caused the error. + +That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE` +environment variable, we'll get a backtrace of exactly how the error happend. +Let's try that. Listing 9-1 shows the output: + +```bash +$ RUST_BACKTRACE=1 cargo run + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running `target/debug/panic` +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is +100', ../src/libcollections/vec.rs:1265 +stack backtrace: + 1: 0x560956150ae9 - +std::sys::backtrace::tracing::imp::write::h482d45d91246faa2 + 2: 0x56095615345c - +std::panicking::default_hook::_{{closure}}::h89158f66286b674e + 3: 0x56095615291e - std::panicking::default_hook::h9e30d428ee3b0c43 + 4: 0x560956152f88 - +std::panicking::rust_panic_with_hook::h2224f33fb7bf2f4c + 5: 0x560956152e22 - std::panicking::begin_panic::hcb11a4dc6d779ae5 + 6: 0x560956152d50 - std::panicking::begin_panic_fmt::h310416c62f3935b3 + 7: 0x560956152cd1 - rust_begin_unwind + 8: 0x560956188a2f - core::panicking::panic_fmt::hc5789f4e80194729 + 9: 0x5609561889d3 - +core::panicking::panic_bounds_check::hb2d969c3cc11ed08 + 10: 0x56095614c075 - _ as +core..ops..Index>::index::hb9f10d3dadbe8101 + at ../src/libcollections/vec.rs:1265 + 11: 0x56095614c134 - panic::main::h2d7d3751fb8705e2 + at /projects/panic/src/main.rs:4 + 12: 0x56095615af46 - __rust_maybe_catch_panic + 13: 0x560956152082 - std::rt::lang_start::h352a66f5026f54bd + 14: 0x56095614c1b3 - main + 15: 0x7f75b88ed72f - __libc_start_main + 16: 0x56095614b3c8 - _start + 17: 0x0 - +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) +``` + + +Listing 9-1: The backtrace generated by a call to `panic!` displayed when +the environment variable `RUST_BACKTRACE` is set + + +That's a lot of output! Line 11 of the backtrace points to the line in our +project causing the problem: `src/main.rs` line four. The key to reading the +backtrace is to start from the top and read until we see files that we wrote: +that's where the problem originated. If we didn't want our program to panic +here, this line is where we would start investigating in order to figure out +how we got to this location with values that caused the panic. + +Now that we've covered how to `panic!` to stop our code's execution and how to +debug a `panic!`, let's look at how to instead return and use recoverable +errors with `Result`. diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md new file mode 100644 index 0000000000..a898a92939 --- /dev/null +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -0,0 +1,368 @@ +## Recoverable Errors with `Result` + +Most errors aren't so dire. Sometimes, when a function fails, it's for a reason +that we can easily interpret and respond to. As an example, maybe we are making +a request to a website, but it's down for maintenance. In this situation, we'd +like to wait and then try again. Terminating our process isn't the right thing +to do here. + +In these cases, Rust's standard library provides an `enum` to use as the return +type of the function: + +```rust +enum Result { + Ok(T), + Err(E), +} +``` + +The `Ok` variant indicates a successful result, and `Err` indicates an +unsuccessful result. These two variants each contain one thing: in `Ok`'s case, +it's the successful return value. With `Err`, it's some value that represents +the error. The `T` and `E` are generic type parameters; we'll go into generics +in more detail in Chapter XX. What you need to know for right now is that the +`Result` type is defined such that it can have the same behavior for any type +`T` that is what we want to return in the success case, and any type `E` that +is what we want to return in the error case. + +Listing 9-2 shows an example of something that might fail: opening a file. + +```rust +use std::fs::File; + +fn main() { + let f = File::open("hello.txt"); +} +``` + + +Listing 9-2: Opening a file + + +The type of `f` in this example is a `Result`, because there are many ways in +which opening a file can fail. For example, unless we created `hello.txt`, this +file does not yet exist. Before we can do anything with our `File`, we need to +extract it out of the result. Listing 9-3 shows one way to handle the `Result` +with a basic tool: the `match` expression that we learned about in Chapter 6. + + + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt"); + + let f = match f { + Ok(file) => file, + Err(error) => panic!("There was a problem opening the file: {:?}", +error), + }; +} +``` + + +Listing 9-3: Using a `match` expression to handle the `Result` variants we might have + + +If we see an `Ok`, we can return the inner `file` out of the `Ok` variant. If +we see `Err`, we have to decide what to do with it. The simplest thing is to +turn our error into a `panic!` instead, by calling the macro. And since we +haven't created that file yet, we'll see a message indicating as such when we +print the error value: + +```bash +thread 'main' panicked at 'There was a problem opening the file: Error { repr: +Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 +``` + +### Matching on Different Errors + +There are many reasons why opening a file might fail, and we may not want to +take the same actions to try to recover for all of them. For example, if the +file we're trying to open does not exist, we could choose to create it. If the +file exists but we don't have permission to read it, or any other error, we +still want to `panic!` in the same way as above and not create the file. + +The `Err` type `File::open` returns is [`io::Error`][ioerror], +which is a struct provided by the standard library. This struct has a method +`kind` that we can call to get an [`io::ErrorKind`][iokind] +value that we can use to handle different causes of an `Err` returned from +`File::open` differently as in Listing 9-4: + +[ioerror]: ../std/io/struct.Error.html +[iokind]: ../std/io/enum.ErrorKind.html + +```rust,ignore +use std::fs::File; +use std::io::ErrorKind; + +fn main() { + let f = File::open("hello.txt"); + + let f = match f { + Ok(file) => file, + Err(ref error) if error.kind() == ErrorKind::NotFound => { + match File::create("hello.txt") { + Ok(fc) => fc, + Err(e) => panic!("Tried to create file but there was a problem: {:?}", e), + } + }, + Err(error) => panic!("There was a problem opening the file: {:?}", +error), + }; +} +``` + + +Listing 9-4: Handling different kinds of errors in different ways + + + + +This example uses a *match guard* with the second arm's pattern to add a +condition that further refines the pattern. The `ref` in the pattern is needed +so that the `error` is not moved into the guard condition. The condition we +want to check is that the value `error.kind()` returns is the `NotFound` +variant of the `ErrorKind` enum. Note that `File::create` could also fail, so +we need to add an inner `match` statement as well! The last arm of the outer +`match` stays the same to panic on any error besides the file not being found. + +### Shortcuts for Panic on Error: `unwrap` and `expect` + +Using `match` works okay but can be a bit verbose, and it doesn't always +communicate intent well. The `Result` type has many helper methods +defined on it to do various things. "Panic on an error result" is one of those +methods, and it's called `unwrap()`: + + + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt").unwrap(); +} +``` + +This has similar behavior as the example using `match` in Listing 9-3: If the +call to `open()` returns `Ok`, return the value inside. If it's an `Err`, panic. + +There's also another method that is similar to `unwrap()`, but lets us choose +the error message: `expect()`. Using `expect()` instead of `unwrap()` and +providing good error messages can convey your intent and make tracking down the +source of a panic easier. `expect()` looks like this: + + + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt").expect("Failed to open hello.txt."); +} +``` + +This isn't the only way to deal with errors, however. This entire section is +supposed to be about recovering from errors, but we've gone back to panic. This +observation gets at an underlying truth: you can easily turn a recoverable +error into an unrecoverable one with `unwrap()` or `expect()`, but you can't +turn an unrecoverable `panic!` into a recoverable one. This is why good Rust +code chooses to make errors recoverable: you give your caller choices. + +The Rust community has a love/hate relationship with `unwrap()` and `expect()`. +They're very handy when prototyping, before you're ready to decide how to +handle errors, and in that case they leave clear markers to look for when you +are ready to make your program more robust. They're useful in tests since they +will cause the test to fail if there's an error any place you call them. In +examples, you might not want to muddy the code with proper error handling. But +if you use them in a library, mis-using your library can cause other people's +programs to halt unexpectedly, and that's not very user-friendly. + +Another time it's appropriate to call `unwrap` is when we have some other logic +that ensures the `Result` will have an `Ok` value, but the logic isn't +something the compiler understands. If you can ensure by manually inspecting +the code that you'll never have an `Err` variant, it is perfectly acceptable to +call `unwrap`. Here's an example: + +```rust +use std::net::IpAddr; + +let home = "127.0.0.1".parse::().unwrap(); +``` + +We're creating an `IpAddr` instance by parsing a hardcoded string. We can see +that `"127.0.0.1"` is a valid IP address, so it's acceptable to use `unwrap` +here. If we got the IP address string from a user of our program instead of +hardcoding this value, we'd definitely want to handle the `Result` in a more +robust way instead. + +### Propagating errors with `try!` or `?` + +When writing a function, if you don't want to handle the error where you are, +you can return the error to the calling function. For example, Listing 9-5 +shows a function that reads a username from a file. If the file doesn't exist +or can't be read, this function will return those errors to the code that +called this function: + +```rust +# use std::fs::File; +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let f = File::open("hello.txt"); + + let mut f = match f { + Ok(file) => file, + Err(e) => return Err(e), + }; + + let mut s = String::new(); + + match f.read_to_string(&mut s) { + Ok(_) => Ok(s), + Err(e) => Err(e), + } +} +``` + + +Listing 9-5: A function that returns errors to the calling code using `match` + + +Since the `Result` type has two type parameters, we need to include them both +in our function signature. In this case, `File::open` and `read_to_string` +return `std::io::Error` as the value inside the `Err` variant, so we will also +use it as our error type. If this function succeeds, we want to return the +username as a `String` inside the `Ok` variant, so that is our success type. + +This is a very common way of handling errors: propagate them upward until +you're ready to deal with them. This pattern is so common in Rust that there is +a macro for it, `try!`, and as of Rust 1.14 , dedicated syntax for it: the question mark +operator. We could have written the code in Listing 9-5 using the `try!` macro, +as in Listing 9-6, and it would have the same functionality as the `match` +expressions: + + + +```rust +# use std::fs::File; +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let mut f = try!(File::open("hello.txt")); + let mut s = String::new(); + + try!(f.read_to_string(&mut s)); + + Ok(s) +} +``` + + +Listing 9-6: A function that returns errors to the calling code using `try!` + + +Or as in Listing 9-7, which uses the question mark operator: + + + +```rust +#![feature(question_mark)] +# fn main() {} +# use std::fs::File; +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let mut f = File::open("hello.txt")?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + Ok(s) +} +``` + +Listing 9-7: A function that returns errors to the calling code using `?` + + +The `?` operator at the end of the `open` call does the same thing as the +example that uses `match` and the example that uses the `try!` macro: It will +return the value inside an `Ok` to the binding `f`, but will return early out +of the whole function and give any `Err` value we get to our caller. The same +thing applies to the `?` at the end of the `read_to_string` call. + +The advantage of using the question mark operator over the `try!` macro is the +question mark operator permits chaining. We could further shorten this code +by instead doing: + +```rust +#![feature(question_mark)] +# fn main() {} +# use std::fs::File; +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let mut s = String::new(); + + File::open("hello.txt")?.read_to_string(&mut s)?; + + Ok(s) +} +``` + +Much nicer, right? The `try!` macro and the `?` operator make propagating +errors upwards much more ergonomic. There's one catch though: they can only be +used in functions that return a `Result`, since they expand to the same `match` +expression we saw above that had a potential early return of an `Err` value. +Let's look at what happens if we try to use `try!` in the `main` function, +which you'll recall has a return type of `()`: + +```rust,ignore +# use std::fs::File; +fn main() { + let f = try!(File::open("hello.txt")); +} +``` + + + +When we compile this, we get the following error message: + +```bash +error[E0308]: mismatched types + --> + | +3 | let f = try!(File::open("hello.txt")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result` + | + = note: expected type `()` + = note: found type `std::result::Result<_, _>` +``` + +The mismatched types that this error is pointing out says the `main()` function +has a return type of `()`, but the `try!` macro might return a `Result`. So in +functions that don't return `Result`, when you call other functions that return +`Result`, you'll need to use a `match` or one of the methods on `Result` to +handle it instead of using `try!` or `?`. + +Now that we've discussed the details of calling `panic!` or returning `Result`, +let's return to the topic of how to decide which is appropriate in which cases. diff --git a/src/ch09-03-to-panic-or-not-to-panic.md b/src/ch09-03-to-panic-or-not-to-panic.md new file mode 100644 index 0000000000..94e38cea7f --- /dev/null +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -0,0 +1,172 @@ +## To `panic!` or Not To `panic!` + +So how do you decide when you should call `panic!` and when you should return +`Result`? A good default for a function that might fail is to return `Result` +since that gives the caller of your function the most flexibility. + +But that answer is simplistic. There are cases where you might want to call +`panic!` in library code that have to do with Rust's quest for safety. Let's +look at some more nuanced guidelines. + +### Guidelines for Error Handling + +`panic!` when your code is in a situation where it's possible to be in a bad +state and: + +* The bad state is not something that's *expected* to happen occasionally +* Your code after this point needs to rely on not being in this bad state +* There's not a good way to encode this information in the types you use + +By *bad state*, we mean some assumption, guarantee, contract, or invariant has +been broken. Some examples are invalid values, contradictory values, or +nothing when you expect to have something. If someone calls your code and +passes in values that don't make sense, the best thing might be to `panic!` and +alert the person using your library to the bug in their code so that they can +fix it during development. Similarly, `panic!` is often appropriate if you call +someone else's code that is out of your control, and it returns an invalid +state that you have no way of fixing. + +Taking each point in turn: + +Some bad states are expected to happen sometimes, and will happen no matter how +well you write your code. Examples of this include a parser being given +malformed data to parse, or an HTTP request returning a status that indicates +you have hit a rate limit. In these cases, you should indicate that failure is +an expected possibility by returning a `Result` and propagate these bad states +upwards so that the caller can decide how they would like to handle the +problem. `panic!` would not be the best way to handle these cases. + +When your code performs operations on values, your code should verify the +values are valid first, then proceed confidently with the operations. This is +mostly for safety reasons: attempting to operate on invalid data can expose +your code to vulnerabilities. This is the main reason that the standard library +will `panic!` if you attempt an out-of-bounds array access: trying to access +memory that doesn't belong to the current data structure is a common security +problem. Functions often have *contracts*: their behavior is only guaranteed if +the inputs meet particular requirements. Panicking when the contract is +violated makes sense because a contract violation always indicates a +caller-side bug, and it is not a kind of error you want callers to have to +explicitly handle. In fact, there's no reasonable way for calling code to +recover: the calling *programmers* need to fix the code. Contracts for a +function, especially when a violation will cause a `panic`, should be explained +in the API documentation for the function. + +Having lots of error checks in all of your functions would be verbose and +annoying, though. Luckily, our last guideline has a tip for this situation: use +Rust's type system (and thus the type checking the compiler does) to do a lot +of the checks for you. If your function takes a particular type as an argument, +you can proceed with your code's logic knowing that the compiler has already +ensured you have a valid value. For example, if you have a type rather than an +`Option`, you know that you will have something rather than nothing and you +don't have to have an explicit check to make sure. Another example is using an +unsigned integer type like `u32`, which ensures the argument value is never +negative. + +### Creating Custom Types for Validation + +Going a step further with the idea of using Rust's type system to ensure we +have a valid value, let's look at an example of creating a custom type for +validation. Recall the guessing game in Chapter 2, where our code asked the user +to guess a number between 1 and 100. We actually never validated that the +user's guess was between those numbers before checking it against our secret +number, only that it was positive. In this case, the consequences were not very +dire: our output of "Too high" or "Too low" would still be correct. It would be +a nice enhancement to guide the user towards valid guesses, though. We could +add a check after we parse the guess: + +```rust,ignore +loop { + // snip + + let guess: u32 = match guess.trim().parse() { + Ok(num) => num, + Err(_) => continue, + }; + + if guess < 1 || guess > 100 { + println!("The secret number will be between 1 and 100."); + continue; + } + + match guess.cmp(&secret_number) { + // snip +} +``` + + + +The `if` expression checks to see if our value is out of range, tells the user +about the problem, and calls `continue` to start the next iteration of the loop +and ask for another guess. After the `if` expression, we can proceed with the +comparisons between `guess` and the secret number knowing that guess is between +1 and 100. + +If we had a situation where it was absolutely critical we had a value between 1 +and 100, and we had many functions that had this requirement, it would be +tedious (and potentially impact performance) to have a check like this in every +function. Instead, we can make a new type and put the validations in one place, +in the type's constructor. Then our functions can use the type with the +confidence that we have values that meet our requirements. Listing 9-8 shows +one way to define a `Guess` type that will only create an instance of `Guess` +if the `new` function gets a value between 1 and 100: + +```rust +struct Guess { + value: u32, +} + +impl Guess { + pub fn new(value: u32) -> Guess { + if value < 1 || value > 100 { + panic!("Guess value must be between 1 and 100, got {}.", value); + } + + Guess { + value: value, + } + } + + pub fn value(&self) -> u32 { + self.value + } +} +``` + + +Listing 9-8: A `Guess` type that will only hold values between 1 and 100 + + +If code calling `Guess::new` passed in a value that was not between 1 and 100, +that would be a violation of the contract that `Guess::new` is relying on. This +function needs to signal to the calling code that it has a bug somewhere +leading to the contract violation. The conditions in which `Guess::new` might +panic should be discussed in its public-facing API documentation, which we will +cover in Chapter XX. + +Important to note is the `value` field of the `Guess` struct is private, so +code using this struct may not set that value directly. Callers *must* use the +`Guess::new` constructor function to create an instance of `Guess`, and they +may read the value using the public `value` function, but they may not access +the field directly. This means any created instance of `Guess` that does not +cause a `panic!` when `new` is called is guaranteed to return numbers between 1 +and 100 from its `value` function. + +A function that takes as an argument or returns only numbers between 1 and 100 +could then declare in its signature to take a `Guess` rather than a `u32`, and +would not need to do any additional checks in its body. + +## Summary + +Rust's error handling features are designed to help you write more robust code. +The `panic!` macro signals that your program is in a state it can't handle, and +lets you tell the process to stop instead of trying to proceed with invalid or +incorrect values. The `Result` enum uses Rust's type system as a sign that +operations you call might fail in a way that your code could recover from. You +can use `Result` to tell code that calls yours that it needs to handle +potential success or failure as well. Using `panic!` and `Result` in the +appropriate situations will help your code be more reliable in the face of +inevitable problems. + +Now that we've seen useful ways that the standard library uses generics with +the `Option` and `Result` enums, let's talk about how generics work and how you +can make use of them in your code.