diff --git a/second-edition/src/ch01-02-hello-world.md b/second-edition/src/ch01-02-hello-world.md index e9d591926a..e8a20ac684 100644 --- a/second-edition/src/ch01-02-hello-world.md +++ b/second-edition/src/ch01-02-hello-world.md @@ -88,6 +88,10 @@ requires these around all function bodies. It's considered good style to put the opening curly brace on the same line as the function declaration, with one space in between. +If you're familiar with languages like C, you may be wondering why `main` +doesn't return a value. We'll talk about that when we talk about error +handling, in Chapter 9.3. + Inside the `main` function: ```rust @@ -95,8 +99,13 @@ Inside the `main` function: ``` This line does all of the work in this little program: it prints text to the -screen. There are a number of details to notice here. The first is that Rust -style is to indent with four spaces, not a tab. +screen. (Technically, it prints text on the _standard output stream_, which +is displayed on the screen by your terminal window. You may be familiar with +standard output already from other programming languages; if not, we will +talk about it more in Chapters 9.3 and 12.6.) + +There are a number of details to notice here. The first is that Rust style is +to indent with four spaces, not a tab. The second important part is `println!`. This is calling a Rust *macro*, which is how metaprogramming is done in Rust. If it were calling a function @@ -106,7 +115,9 @@ that when you see a `!` that means that you’re calling a macro instead of a normal function. Next is `"Hello, world!"` which is a *string*. We pass this string as an -argument to `println!`, which prints the string to the screen. Easy enough! +argument to `println!`, which prints it. Easy enough! `println!` can print +other things as well as strings; you'll see more ways to use it as we go on. + The line ends with a semicolon (`;`). The `;` indicates that this expression is over, and the next one is ready to begin. Most lines of Rust code end with a diff --git a/second-edition/src/ch02-00-guessing-game-tutorial.md b/second-edition/src/ch02-00-guessing-game-tutorial.md index c6adb4adec..43733cbe49 100644 --- a/second-edition/src/ch02-00-guessing-game-tutorial.md +++ b/second-edition/src/ch02-00-guessing-game-tutorial.md @@ -290,9 +290,10 @@ src/main.rs:10 io::stdin().read_line(&mut guess); ``` Rust warns that we haven’t used the `Result` value returned from `read_line`, -indicating that the program hasn’t handled a possible error. The right way to -suppress the warning is to actually write error handling, but since we just -want to crash this program when a problem occurs, we can use `expect`. You’ll +indicating that the program hasn’t handled a possible error. + +Normally you don’t want your program to crash just because it failed to read +some input, but since we’re just getting started, it’s OK for now. You’ll learn about recovering from errors in Chapter 9. ### Printing Values with `println!` Placeholders @@ -304,11 +305,13 @@ the code added so far, which is the following: println!("You guessed: {}", guess); ``` -This line prints out the string we saved the user’s input in. The set of `{}` -is a placeholder that holds a value in place. You can print more than one value -using `{}`: the first set of `{}` holds the first value listed after the format -string, the second set holds the second value, and so on. Printing out multiple -values in one call to `println!` would look like this: +This prints the string we read on the previous line, with `You guessed: ` in +front. We are now giving `println!` two arguments. The first argument is +called the _format string_, and the second argument is a _value_ to print. +When used this way, `println!` prints the text of the format string, but where +the curly braces `{}` appear, it substitutes the value. `{}` is called a +_placeholder_ for the value. You can use more than one `{}`, along with more +than one value: ```rust let x = 5; @@ -317,7 +320,8 @@ let y = 10; println!("x = {} and y = {}", x, y); ``` -This code would print out `x = 5 and y = 10`. +This code would print out `x = 5 and y = 10`. Notice that you can print +numbers this way, as well as strings. ### Testing the First Part diff --git a/second-edition/src/ch06-02-match.md b/second-edition/src/ch06-02-match.md index 061693d6b0..7239a0cacf 100644 --- a/second-edition/src/ch06-02-match.md +++ b/second-edition/src/ch06-02-match.md @@ -96,7 +96,7 @@ values that match the pattern. This is how we can extract values out of enum variants. As an example, let’s change one of our enum variants to hold data inside it. -From 1999 through 2008, the United States printed quarters with different +From 1999 through 2008, the United States minted quarters with different designs for each of the 50 states on one side. No other coins got state designs, so only quarters have this extra value. We can add this information to our `enum` by changing the `Quarter` variant to include a `State` value stored diff --git a/second-edition/src/ch09-02-recoverable-errors-with-result.md b/second-edition/src/ch09-02-recoverable-errors-with-result.md index 17a47b5b19..7ca1d1919f 100644 --- a/second-edition/src/ch09-02-recoverable-errors-with-result.md +++ b/second-edition/src/ch09-02-recoverable-errors-with-result.md @@ -103,7 +103,7 @@ fn main() { let f = match f { Ok(file) => file, Err(error) => { - panic!("There was a problem opening the file: {:?}", error) + panic!("There was a problem opening 'hello.txt': {:?}", error) }, }; } @@ -127,7 +127,7 @@ there’s no file named *hello.txt* in our current directory and we run this code, we’ll see the following output from the `panic!` macro: ```text -thread 'main' panicked at 'There was a problem opening the file: Error { repr: +thread 'main' panicked at 'There was a problem opening 'hello.txt': Error { repr: Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 ``` @@ -158,7 +158,7 @@ fn main() { Ok(fc) => fc, Err(e) => { panic!( - "Tried to create file but there was a problem: {:?}", + "Tried to create 'hello.txt' but there was a problem: {:?}", e ) }, @@ -166,7 +166,7 @@ fn main() { }, Err(error) => { panic!( - "There was a problem opening the file: {:?}", + "There was a problem opening 'hello.txt': {:?}", error ) }, diff --git a/second-edition/src/ch09-03-to-panic-or-not-to-panic.md b/second-edition/src/ch09-03-to-panic-or-not-to-panic.md index f203a1200d..182f2073f4 100644 --- a/second-edition/src/ch09-03-to-panic-or-not-to-panic.md +++ b/second-edition/src/ch09-03-to-panic-or-not-to-panic.md @@ -1,23 +1,119 @@ ## To `panic!` or Not To `panic!` So how do you decide when you should `panic!` and when you should return -`Result`? When code panics, there’s no way to recover. You could choose to call -`panic!` for any error situation, whether there’s a possible way to recover or -not, but then you’re making the decision for your callers that a situation is -unrecoverable. When you choose to return a `Result` value, you give your caller -options, rather than making the decision for them. They could choose to attempt -to recover in a way that’s appropriate for their situation, or they could -decide that actually, an `Err` value in this case is unrecoverable, so they can -call `panic!` and turn your recoverable error into an unrecoverable one. +`Result`? As a general rule, we recommend you only use `panic!` for situations +where the error indicates a bug in the program. For instance, library +functions often have *contracts*: their inputs must meet inputs particular +requirements, and if they don't, the calling code has a bug. Panicking when +the contract is violated makes sense because a contract violation always +indicates a bug in the program, and trying to recover may only make matters +worse. 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. Contracts +for a function, especially when a violation will cause a panic, should be +explained in the API documentation for the function. + +If you design your API to take good advantage of Rust's type system, you will +often find that contract checking is unnecessary, because violations cannot +happen. If your function has a particular type as a parameter, 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`, your +program expects to have *something* rather than *nothing*. Your code then +doesn’t have to handle two cases for the `Some` and `None` variants, it will +only have one case for definitely having a value. Code trying to pass nothing +to your function won’t even compile, so your function doesn’t have to check +for that case at runtime. Another example is using an unsigned integer type +like `u32`, which ensures the parameter is never negative. + +On the other hand, when errors happen due to a problem _outside_ the program, +that is almost always something you should handle with a `Result`, even if +there's nothing more that the program can do but print an error message and +exit. We've already seen that converting strings into numbers with `parse` +returns a `Result`, because strings containing invalid numbers are usually +_not_ bugs; they are usually because the _input_ to the program was +incorrect. When you try to access files and get errors from the operating +system, that is also not a bug in your program; at worst it indicates that +there's something wrong with the _computer_ that a human needs to fix (Unix +systems don't work very well if `/etc/passwd` doesn't exist). Network servers +might be inaccessible or malfunctioning for reasons completely out of your +control, or they might be refusing service because the user doesn't have the +proper credentials. + +Another thing to keep in mind is that when you use `panic!` you are declaring +that there is nothing a caller could possibly do to recover from the error. +When you choose to return a `Result` value, you give your caller options, +rather than making the decision for them. They could choose to attempt to +recover in a way that’s appropriate for their situation, or they could decide +that actually, an `Err` value in this case is unrecoverable, so they can call +`panic!` and turn your recoverable error into an unrecoverable one. Therefore, returning `Result` is a good default choice when you’re defining a function that might fail. -There are a few situations in which it’s more appropriate to write code that -panics instead of returning a `Result`, but they are less common. Let’s discuss -why it’s appropriate to panic in examples, prototype code, and tests, then -situations where you as a human can know a method won’t fail that the compiler -can’t reason about, and conclude with some general guidelines on how to decide -whether to panic in library code. +### Reporting Errors to the User + +At the highest levels of your program, you will need to intercept error +`Results` and report them to a human. Exactly how to do this will depend on +the environment in which you are running—a command-line tool is different from +a network server or a graphical application. Rust's standard library has all +the facilities needed to report errors in command-line tools, so we will use +that situation for an example. + +Recall from the previous chapter, the function that read text from a file. + +``` rust +use std::io; +use std::io::Read; +use std::fs::File; + +fn read_username_from_file() -> Result { + let mut s = String::new(); + + File::open("hello.txt")?.read_to_string(&mut s)?; + + Ok(s) +} +``` + +If we call this function from `main` in a command-line tool, what should we do +when it returns an error? It's not right to use `expect` or `panic!`, but we +can't use the `?` operator either, because `main` doesn't return anything. +Here's one way to handle it: + +``` rust,ignore +use std::process; + +fn main() { + match read_username_from_file() { + Ok(s) => { + println!("Hello, {}.", s); + }, + Err(e) => { + eprintln!("Failed to read from 'hello.txt': {}", e); + process::exit(1); + } + } +} +``` + +This uses two library features we haven't seen before. `eprintln!` is like +`println!`, except for one thing: it prints text to the standard _error_ +stream, instead of standard output. This prevents error messages from getting +mixed up with the "normal" output of the program—if you've ever tried to print +a document on fancy paper, but what got printed was error messages, you'll +understand why this is important. (The messages printed by `panic!` are +also sent to standard error.) + +`process::exit(1)` ends the program with an _unsuccessful_ "exit code" +reported to the command-line environment. (By convention, an exit code of zero +means success, and any nonzero value is some sort of failure.) Rust doesn't +let you do this by returning a value from `main`, the way C does, because the +whole idea of an "exit code" is peculiar to multiprocessing operating systems +that work essentially the same way Unix does. In other environments it isn't +possible to return an "exit code", or it might even be a bug to return from +`main` at all. But if you're in an environment where exit codes exist, then +`process::exit` will be available, and the number passed to it will mean the +same thing it does in C. And falling off the end of `main`, which is what +happens in the `Ok` case, will be the same as calling `process::exit(0)`. ### Examples, Prototype Code, and Tests: Perfectly Fine to Panic @@ -65,61 +161,6 @@ being hardcoded into the program, and therefore *did* have a possibility of failure, we’d definitely want to handle the `Result` in a more robust way instead. -### Guidelines for Error Handling - -It’s advisable to have your code `panic!` when it’s possible that you could end -up in a bad state—in this context, bad state is when some assumption, -guarantee, contract, or invariant has been broken, such as when invalid values, -contradictory values, or missing values are passed to your code—plus one or -more of the following: - -* 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 - -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’re calling external code that is out of your -control, and it returns an invalid state that you have no way of fixing. - -When a bad state is reached, but it’s expected to happen no matter how well you -write your code, it’s still more appropriate to return a `Result` rather than -calling `panic!`. Examples of this include a parser being given malformed data, -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` in order to propagate these bad states upwards so that the -caller can decide how they would like to handle the problem. To `panic!` -wouldn’t be the best way to handle these cases. - -When your code performs operations on values, your code should verify the -values are valid first, and `panic!` if the values aren’t valid. 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, you can 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 -has a particular type as a parameter, 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`, your program expects to -have *something* rather than *nothing*. Your code then doesn’t have to handle -two cases for the `Some` and `None` variants, it will only have one case for -definitely having a value. Code trying to pass nothing to your function won’t -even compile, so your function doesn’t have to check for that case at runtime. -Another example is using an unsigned integer type like `u32`, which ensures the -parameter is never negative. - ### Creating Custom Types for Validation Let’s take the idea of using Rust’s type system to ensure we have a valid value diff --git a/second-edition/src/ch12-03-improving-error-handling-and-modularity.md b/second-edition/src/ch12-03-improving-error-handling-and-modularity.md index 66070446ff..9105896873 100644 --- a/second-edition/src/ch12-03-improving-error-handling-and-modularity.md +++ b/second-edition/src/ch12-03-improving-error-handling-and-modularity.md @@ -318,7 +318,7 @@ fn main() { let args: Vec = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { - println!("Problem parsing arguments: {}", err); + eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); @@ -349,12 +349,12 @@ pass the inner value of the `Err` to our closure in the parameter `err` that appears between the vertical pipes. Using `unwrap_or_else` lets us do some custom, non-`panic!` error handling. -Said error handling is only two lines: we print out the error, then call -`std::process::exit`. That function will stop our program's execution -immediately and return the number passed to it as a return code. By convention, -a zero means success and any other value means failure. In the end, this has -similar characteristics to our `panic!`-based handling we had in Listing 12-7, -but we no longer get all the extra output. Let's try it: +That custom error handling is very similar to what we saw in Chapter 9.3. We +print out the error with `eprintln!`, so that it is not confused with the +non-error output of the program, and then we use `std::process::exit` to end +the program with a unsuccessful exit code. This is not that different from +the `panic!` we used in Listing 12-7, but we no longer get all the extra +output. Let's try it: ```text $ cargo run @@ -481,8 +481,7 @@ fn main() { println!("In file {}", config.filename); if let Err(e) = run(config) { - println!("Application error: {}", e); - + eprintln!("Application error: {}", e); process::exit(1); } } @@ -593,7 +592,7 @@ fn main() { let args: Vec = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { - println!("Problem parsing arguments: {}", err); + eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); @@ -601,8 +600,7 @@ fn main() { println!("In file {}", config.filename); if let Err(e) = greprs::run(config) { - println!("Application error: {}", e); - + eprintln!("Application error: {}", e); process::exit(1); } } diff --git a/second-edition/src/ch12-04-testing-the-librarys-functionality.md b/second-edition/src/ch12-04-testing-the-librarys-functionality.md index 0381a0c2d2..9398cfa9f5 100644 --- a/second-edition/src/ch12-04-testing-the-librarys-functionality.md +++ b/second-edition/src/ch12-04-testing-the-librarys-functionality.md @@ -235,7 +235,9 @@ improve it. Now that the `grep` function is working, we need to do one last thing inside of the `run` function: we never printed out the results! We'll do that by adding -a `for` loop that prints each line returned from the `grep` function: +a `for` loop that prints each line returned from the `grep` function. +This output is the "normal" output of the program—not an error +message—so we use `println!` for it: Filename: src/lib.rs diff --git a/second-edition/src/ch12-05-working-with-environment-variables.md b/second-edition/src/ch12-05-working-with-environment-variables.md index 467f6a035f..7c5c015ee7 100644 --- a/second-edition/src/ch12-05-working-with-environment-variables.md +++ b/second-edition/src/ch12-05-working-with-environment-variables.md @@ -163,7 +163,8 @@ at the top of *src/lib.rs*: use std::env; ``` -And then use the `vars` method from the `env` module inside of `Config::new`: +And then, inside of `Config::new`, we use the `var_os` method to check +whether the environment variable is set. Filename: src/lib.rs @@ -186,11 +187,8 @@ impl Config { let filename = args[2].clone(); let mut case_sensitive = true; - - for (name, _) in env::vars() { - if name == "CASE_INSENSITIVE" { - case_sensitive = false; - } + if let Some(_) = env::var_os("CASE_INSENSITIVE") { + case_sensitive = false; } Ok(Config { @@ -204,17 +202,21 @@ impl Config { -Here, we call `env::vars`, which works in a similar way as `env::args`. The -difference is `env::vars` returns an iterator of environment variables rather -than command line arguments. Instead of using `collect` to create a vector of -all of the environment variables, we're using a `for` loop. `env::vars` returns -tuples: the name of the environment variable and its value. We never care about -the values, only if the variable is set at all, so we use the `_` placeholder -instead of a name to let Rust know that it shouldn't warn us about an unused -variable. Finally, we have a `case_sensitive` variable, which is set to true by -default. If we ever find a `CASE_INSENSITIVE` environment variable, we set the -`case_sensitive` variable to false instead. Then we return the value as part of -the `Config`. +`var_os` looks up an environment variable and returns an `Option`, which we +saw in Chapter 6.1. It will be `Some(value)` if the variable is set, and +`None` if it isn't. Since we don't care what the value of the +`CASE_SENSITIVE` environment variable is, only whether it's set or not, we use +`if let Some(_)` to throw away the value. (If we _did_ want to examine the +value, we could use `if let Some(s)` instead.) We could also have written + +``` rust,ignore + let case_sensitive = match env::var_os("CASE_INSENSITIVE") { + Some(_) => false, + None => true + }; +``` + +Then we return the value as part of the `Config`. Let's give it a try! @@ -236,9 +238,9 @@ To tell your name the livelong day To an admiring bog! ``` -Excellent! Our `greprs` program can now do case insensitive searching controlled -by an environment variable. Now you know how to manage options set using -either command line arguments or environment variables! +Excellent! Our `greprs` program can now do case insensitive searching +controlled by an environment variable. Now you know how to manage options set +using either command line arguments or environment variables! Some programs allow both arguments _and_ environment variables for the same configuration. In those cases, the programs decide that one or the other of diff --git a/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md b/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md index 1ead7a80a7..78dd7933be 100644 --- a/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md +++ b/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md @@ -1,100 +1,41 @@ -## Write to `stderr` Instead of `stdout` +## Writing to `stderr` instead of `stdout` -Right now, we're writing all of our output to the terminal with `println!`. -This works, but most terminals provide two kinds of output: "standard out" is -used for most information, but "standard error" is used for error messages. This -makes it easier to do things like "Print error messages to my terminal, but -write other output to a file." +In our program, we have been careful to use `eprintln!` for errors and +`println!` for the matching lines. This is because, as we mentioned in Chapter +9.3, command-line programs have two kinds of output: "standard output" is for +the "normal output" of the program, and "standard error" is for error and +progress messages. You may be wondering why this is an important distinction +to make, so we're going to step up a level and illustrate how it's useful. -We can see that our program is only capable of printing to `stdout` by -redirecting it to a file using `>` on the command line, and running our program -without any arguments, which causes an error: - -```text -$ cargo run > output.txt -``` - -The `>` syntax tells the shell to write the contents of standard out to -*output.txt* instead of the screen. However, if we open *output.txt* after -running we'll see our error message: +If you run our program with no command line arguments, it prints an error: ```text +$ cargo run Problem parsing arguments: not enough arguments ``` -We'd like this to be printed to the screen instead, and only have the output -from a successful run end up in the file if we run our program this way. Let's -change how error messages are printed as shown in Listing 12-17: - -Filename: src/main.rs - -```rust,ignore -extern crate greprs; - -use std::env; -use std::process; -use std::io::prelude::*; - -use greprs::Config; - -fn main() { - let mut stderr = std::io::stderr(); - let args: Vec = env::args().collect(); - - let config = Config::new(&args).unwrap_or_else(|err| { - writeln!( - &mut stderr, - "Problem parsing arguments: {}", - err - ).expect("Could not write to stderr"); - - process::exit(1); - }); - - if let Err(e) = greprs::run(config) { - - writeln!( - &mut stderr, - "Application error: {}", - e - ).expect("Could not write to stderr"); - - process::exit(1); - } -} -``` - -Listing 12-17: Writing error messages to `stderr` instead -of `stdout` - - - -Rust does not have a convenient function like `println!` for writing to -standard error. Instead, we use the `writeln!` macro, which is sort of like -`println!`, but it takes an extra argument. The first thing we pass to it is -what to write to. We can acquire a handle to standard error through the -`std::io::stderr` function. We give a mutable reference to `stderr` to -`writeln!`; we need it to be mutable so we can write to it! The second and -third arguments to `writeln!` are like the first and second arguments to -`println!`: a format string and any variables we're interpolating. - -Let's try running the program again in the same way, without any arguments and -redirecting `stdout` with `>`: +That error is printed with `eprintln!`, so it goes to standard error, so it is +_not_ redirected by the shell's `>` operator (which sends standard _output_ to +a file): ```text $ cargo run > output.txt Problem parsing arguments: not enough arguments ``` -Now we see our error on the screen, but `output.txt` contains nothing. If we -try it again with arguments that work: +We still see the error message in the terminal, and `output.txt` will be +empty. If we had used `println!` for the error messages, they would have been +redirected into `output.txt`. We wouldn't have seen them, and `output.txt` +would have unexpected junk in it. + +If we try this again with arguments that work: ```text $ cargo run to poem.txt > output.txt ``` -We'll see no output to our terminal, but `output.txt` will contain -our results: +we'll see no output to our terminal, but `output.txt` will contain our +results: Filename: output.txt