Skip to content

Commit dd84d88

Browse files
committed
Add "Refactoring Pt.2 - Error Handling" section to Chapter 12 and update SUMMARY.md
1 parent 1b697ff commit dd84d88

3 files changed

Lines changed: 172 additions & 2 deletions

File tree

en/src/Chapter-12/12.2/12.2._Read_Files.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Next, read the file using `filename`:
3737
```rust
3838
let contents = fs::read_to_string(filename);
3939
```
40-
Reading can fail, so the return value is not the contents directly but a `Result` enum. For that enum, you can use `expect` to unwrap it. The argument to `expect` is the error message to print if something goes wrong (`expect` is covered in detail in 9.2. Result Enum and Recoverable Errors Pt. 1).
40+
Reading can fail, so the return value is not the contents directly but a `Result` enum. For that enum, you can use `expect` to unwrap it. The argument to `expect` is the error message to print if something goes wrong (`expect` is covered in detail in [9.2. Result Enum and Recoverable Errors Pt. 1](../../Chapter-09/9.2/9.2._Result_Enum_and_Recoverable_Errors_Pt._1_-_Match,_Expect,_and_Unwrap_Handling_Errors.md)).
4141
```rust
4242
let contents = fs::read_to_string(filename)
4343
.expect("Something went wrong while reading the file");// line break here is only to keep the line short
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# 12.4 Refactoring Pt.2 - Error Handling
2+
3+
## 12.4.0 Before We Begin
4+
In Chapter 12, we will build a real project: a command-line program. This program is a `grep` (**Global Regular Expression Print**), a tool for global regular-expression search and output. Its job is to **search for the specified text in the specified file.**
5+
6+
This project has several steps:
7+
- [Receive command-line arguments](../12.1/12.1._Receiving_Command-Line_Arguments.md)
8+
- [Read files](../12.2/12.2._Read_Files.md)
9+
- Refactor: improve modules and error handling (this article)
10+
- Use TDD (test-driven development) to develop library functionality
11+
- Use environment variables
12+
- Write error messages to standard error instead of standard output
13+
14+
## 12.4.1 Review
15+
In the previous section, to improve modularity, we created a struct for the variables and moved the argument-parsing function into a method on that struct. Here is all the code written up to the previous article:
16+
```rust
17+
use std::env;
18+
use std::fs;
19+
20+
struct Config {
21+
query: String,
22+
filename: String,
23+
}
24+
25+
fn main() {
26+
let args:Vec<String> = env::args().collect();
27+
let config = Config::new(&args);
28+
29+
let contents = fs::read_to_string(config.filename)
30+
.expect("Something went wrong while reading the file");
31+
println!("With text:\n{}", contents);
32+
}
33+
34+
impl Config {
35+
fn new(args: &[String]) -> Config {
36+
let query = args[1].clone();
37+
let filename = args[2].clone();
38+
Config {
39+
query,
40+
filename,
41+
}
42+
}
43+
}
44+
```
45+
46+
## 12.4.2 Unexpected Input
47+
The program works correctly only when the user provides valid input. Let’s try running it without arguments:
48+
```
49+
$ cargo run
50+
Compiling minigrep v0.1.0 (file:///projects/minigrep)
51+
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
52+
Running `target/debug/minigrep`
53+
thread 'main' panicked at src/main.rs:27:21:
54+
index out of bounds: the len is 1 but the index is 1
55+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
56+
```
57+
It reports `Index out of bounds`. As the programmer, we know this is because there were not enough arguments, so the program went out of bounds when using an index to fetch them. But a user cannot understand this error message, so they cannot correct the mistake.
58+
59+
What this article will do is make the program’s error messages easier to understand.
60+
61+
## 12.4.3 Specifying Error Messages
62+
The way to help users understand the error is to provide our own error message. In the previous example, the panic happened when `Config::new` tried to access an out-of-bounds index, so let’s modify that part:
63+
```rust
64+
impl Config {
65+
fn new(args: &[String]) -> Config {
66+
if args.len() < 3 {
67+
panic!("Not enough arguments");
68+
}
69+
let query = args[1].clone();
70+
let filename = args[2].clone();
71+
Config {
72+
query,
73+
filename,
74+
}
75+
}
76+
}
77+
```
78+
If `args` contains fewer than three elements, panic and print `"Not enough arguments"` to tell the user that too few arguments were provided.
79+
80+
Try running it without arguments again:
81+
```
82+
$ cargo run
83+
Compiling minigrep v0.1.0 (file:///projects/minigrep)
84+
Finished `dev` profile [unoptimized +debuginfo] target(s) in 0.0s
85+
Running `target/debug/minigrep`
86+
thread 'main' panicked at src/main.rs:26:13:
87+
not enough arguments
88+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
89+
```
90+
This error message is much better than the previous one.
91+
92+
However, some extra information is still shown, such as `thread 'main' panicked at src/main.rs:26:13:` and `note: run with RUST_BACKTRACE=1 environment variable to display a backtrace`. Those are for programmers, not for users, so they should be removed too.
93+
94+
## 12.4.4 Using the `Result` Type
95+
`panic!` is appropriate when the program itself has a bug. But here, the problem is that the program was used incorrectly because there were too few arguments. For this kind of problem, using `Result` to propagate the error is the best choice (see [9.2. Result Enum and Recoverable Errors Pt. 1](../../Chapter-09/9.2/9.2._Result_Enum_and_Recoverable_Errors_Pt._1_-_Match,_Expect,_and_Unwrap_Handling_Errors.md) and [Pt. 2](../../Chapter-09/9.3/9.3._Result_Enum_and_Recoverable_Errors_Pt._2_-_Error_Propagation,_Question_Mark_Operator,_and_Chained_Calls.md) for details):
96+
```rust
97+
impl Config {
98+
fn new(args: &[String]) -> Result<Config, &'static str> {
99+
if args.len() < 3 {
100+
return Err("Not enough arguments");
101+
}
102+
let query = args[1].clone();
103+
let filename = args[2].clone();
104+
Ok(Config { query, filename})
105+
}
106+
}
107+
```
108+
- Error information must be wrapped in `Err`, and successful return values must be wrapped in `Ok`.
109+
- The `Ok` variant of `Result` returns a `Config` instance, while `Err` returns an `&str` string literal. However, the compiler does not know where that `&str` comes from or how long its lifetime is, so we need a lifetime annotation. We want it to remain valid for the entire program run, so we write it as `&'static str`, the static lifetime.
110+
111+
Since the return type of `new` has changed, the code in `main` that receives its value must also change:
112+
```rust
113+
let config = Config::new(&args).unwrap_or_else(|err| {
114+
println!("Problem parsing arguments: {}", err);
115+
process::exit(1);
116+
});
117+
```
118+
The `unwrap_or_else` method accepts a `Result`. If it is `Ok`, it returns the value inside `Ok`, similar to `unwrap`. If it is `Err`, the method calls a closure.
119+
120+
A closure is an anonymous function that we define and pass as an argument to `unwrap_or_else`. Its syntax uses two pipes `||`, with a variable name in the middle as the parameter. Here it is `err`, which can be used inside the closure body, such as when printing the error.
121+
122+
Then we use `process::exit` from the standard library. Remember to import it first with `use std::process;`. Calling `exit` terminates the program immediately, and its argument, `1` in the example, becomes the program’s exit status code. This means that after `println!("Problem parsing arguments: {}", err);`, the program stops, so there is no `thread 'main' panicked at src/main.rs:26:13:` or backtrace note.
123+
124+
Try it:
125+
```
126+
$ cargo run
127+
Compiling minigrep v0.1.0 (file:///projects/minigrep)
128+
Finished `dev` profile [unoptimized +debuginfo] target(s) in 0.48s
129+
Running `target/debug/minigrep`
130+
Problem parsing arguments: not enough arguments
131+
```
132+
133+
**The concept of closures will be covered in the next chapter, so it is fine if this does not make complete sense yet; a rough understanding is enough here.**
134+
135+
## 12.4.5 The Full Code
136+
Here is all the code written up to this article:
137+
```rust
138+
use std::env;
139+
use std::fs;
140+
use std::process;
141+
142+
struct Config {
143+
query: String,
144+
filename: String,
145+
}
146+
147+
fn main() {
148+
let args:Vec<String> = env::args().collect();
149+
let config = Config::new(&args).unwrap_or_else(|err| {
150+
println!("Problem parsing arguments: {}", err);
151+
process::exit(1);
152+
});
153+
154+
let contents = fs::read_to_string(config.filename)
155+
.expect("Something went wrong while reading the file");
156+
println!("With text:\n{}", contents);
157+
}
158+
159+
impl Config {
160+
fn new(args: &[String]) -> Result<Config, &'static str> {
161+
if args.len() < 3 {
162+
return Err("Not enough arguments");
163+
}
164+
let query = args[1].clone();
165+
let filename = args[2].clone();
166+
Ok(Config { query, filename})
167+
}
168+
}
169+
```

en/src/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,5 @@
6262
- [Integration Tests](./Chapter-11/11.10/11.10._Integration_Tests.md)
6363
- [Receiving Command-Line Arguments](./Chapter-12/12.1/12.1._Receiving_Command-Line_Arguments.md)
6464
- [Read Files](./Chapter-12/12.2/12.2._Read_Files.md)
65-
- [Refactoring Pt.1 - Improving Modularity](./Chapter-12/12.3/12.3._Refactoring_Pt.1_-_Improving_Modularity.md)
65+
- [Refactoring Pt.1 - Improving Modularity](./Chapter-12/12.3/12.3._Refactoring_Pt.1_-_Improving_Modularity.md)
66+
- [Refactoring Pt.2 - Error Handling](./Chapter-12/12.4/12.4._Refactoring_Pt.2_-_Error_Handling.md)

0 commit comments

Comments
 (0)