Skip to content

Commit e98eab8

Browse files
committed
Reworking Result examples til I hit the horrible Carrier error
1 parent 6d2a2c4 commit e98eab8

File tree

1 file changed

+106
-29
lines changed

1 file changed

+106
-29
lines changed

src/ch09-02-recoverable-errors-with-result.md

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ fn main() {
8383
}
8484
```
8585

86-
This has the same behavior as our previous example: If the call to `open()`
86+
This has similar behavior as our previous example: If the call to `open()`
8787
returns `Ok`, return the value inside. If it's an `Err`, panic.
8888

8989
There's also another method, similar to `unwrap()`, but that lets us choose the
@@ -97,7 +97,7 @@ a panic easier. `expect()` looks like this:
9797
use std::fs::File;
9898
9999
fn main() {
100-
let f = File::open("hello.txt").expect("failed to open hello.txt");
100+
let f = File::open("hello.txt").expect("Failed to open hello.txt.");
101101
}
102102
```
103103

@@ -109,55 +109,132 @@ turn an unrecoverable `panic!` into a recoverable one. This is why good Rust
109109
code chooses to make errors recoverable: you give your caller choices.
110110

111111
The Rust community has a love/hate relationship with `unwrap()` and `expect()`.
112-
They're useful in tests since they will cause the test to fail if there's an
113-
error anyplace you call them. In examples, you might not want to muddy the code
114-
with proper error handling. But if you use them in a library, mis-using your
115-
library can cause other people's programs to halt unexpectedly, and that's not
116-
very user-friendly.
112+
They're very handy when prototyping, before you're ready to decide how to
113+
handle errors, and in that case they leave clear markers to look for when you
114+
are ready to make your program more robust. They're useful in tests since they
115+
will cause the test to fail if there's an error anyplace you call them. In
116+
examples, you might not want to muddy the code with proper error handling. But
117+
if you use them in a library, mis-using your library can cause other people's
118+
programs to halt unexpectedly, and that's not very user-friendly.
117119

118120
## Propagating errors with `?`
119121

120122
When writing a function, if you don't want to handle the error where you are,
121-
you can return the error to the calling function. Within your function, that
122-
would look like:
123+
you can return the error to the calling function. For example, here's a
124+
function that reads a username from a file. If the file doesn't exist or can't
125+
be read, this function will return those errors to the code that called this
126+
function:
123127

124-
<!-- I'll ghost everything except `return Err(e)` in the libreoffice file /Carol -->
125-
126-
```rust,ignore
128+
```rust
127129
# use std::fs::File;
128-
# fn foo() -> std::io::Result<()> {
129-
let f = File::open("hello.txt");
130+
# use std::io;
131+
# use std::io::Read;
132+
#
133+
fn read_username_from_file() -> Result<String, io::Error> {
134+
let f = File::open("hello.txt");
135+
136+
let mut f = match f {
137+
Ok(file) => file,
138+
Err(e) => return Err(e),
139+
};
130140

131-
let f = match f {
132-
Ok(file) => file,
133-
Err(e) => return Err(e),
134-
};
141+
let mut s = String::new();
135142

136-
# Ok(())
137-
# }
143+
match f.read_to_string(&mut s) {
144+
Ok(_) => Ok(s),
145+
Err(e) => Err(e),
146+
}
147+
}
138148
```
139149

140150
This is a very common way of handling errors: propagate them upward until
141151
you're ready to deal with them. This pattern is so common in Rust that there is
142-
dedicated syntax for it: the question mark operator. We could have also written
143-
the example like this:
152+
a macro for it, `try!`, and as of Rust 1.XX, dedicated syntax for it: the
153+
question mark operator. We could have written the above like this using the
154+
`try!` macro and it would have the same functionality as the `match` expressions:
144155

145-
<!-- I'll ghost everything except `?` in the libreoffice file /Carol -->
156+
<!-- I'll ghost everything except the calls to `try!` in the libreoffice file
157+
/Carol -->
146158

147-
```rust,ignore
159+
```rust
160+
# use std::fs::File;
161+
# use std::io;
162+
# use std::io::Read;
163+
#
164+
fn read_username_from_file() -> Result<String, io::Error> {
165+
let mut f = try!(File::open("hello.txt"));
166+
let mut s = String::new();
167+
try!(f.read_to_string(&mut s));
168+
Ok(s)
169+
}
170+
```
171+
172+
Or like this using the question mark operator:
173+
174+
<!-- I'll ghost everything except the question mark operator in the libreoffice
175+
file. Also note the `#![feature(question_mark)]` line won't be needed once this
176+
feature has made it into a stable version of Rust, which will happen well
177+
before the book's publication. /Carol -->
178+
179+
```rust
180+
#![feature(question_mark)]
181+
# fn main() {}
182+
# use std::fs::File;
183+
# use std::io;
184+
# use std::io::Read;
185+
#
186+
fn read_username_from_file() -> Result<String, io::Error> {
187+
let mut f = File::open("hello.txt")?;
188+
let mut s = String::new();
189+
f.read_to_string(&mut s)?;
190+
Ok(s)
191+
}
192+
```
193+
194+
The `?` operator at the end of the `open` call does the same thing as the
195+
example that uses `match` and the example that uses the `try!` macro: It will
196+
return the value inside an `Ok` to the binding `f`, but will return early out
197+
of the whole function and give any `Err` value we get to our caller. The same
198+
thing applies to the `?` at the end of the `read_to_string` call.
199+
200+
The advantage of using the question mark operator over the `try!` macro is the
201+
question mark operator permits chaining. We could further shorten this code
202+
by instead doing:
203+
204+
```rust
148205
#![feature(question_mark)]
206+
# fn main() {}
207+
# use std::fs::File;
208+
# use std::io;
209+
# use std::io::Read;
210+
#
211+
fn read_username_from_file() -> Result<String, io::Error> {
212+
let mut s = String::new();
213+
File::open("hello.txt")?.read_to_string(&mut s)?;
214+
Ok(s)
215+
}
216+
```
149217

150-
use std::fs::File;
218+
Much nicer, right? The `try!` macro and the `?` operator make propagating
219+
errors upwards much more ergonomic. There's one catch though: they can only be
220+
used in functions that return a `Result`, since they expand to the same `match`
221+
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 `?` in the `main` function, which has a return type of `()`:
151222

223+
```rust,ignore
224+
#![feature(question_mark)]
225+
# use std::fs::File;
152226
fn main() {
153227
let f = File::open("hello.txt")?;
154228
}
155229
```
156230

157-
The `?` operator at the end of the `open` call does the same thing as our
158-
previous example: It will return the value inside an `Ok` to the binding `f`,
159-
but will return early out of the whole function and give any `Err` value we get
160-
to our caller.
231+
When we compile this, we get the following error message:
232+
233+
```bash
234+
```
235+
236+
237+
161238

162239
There's one problem though; let's try compiling the example:
163240

0 commit comments

Comments
 (0)