]>
Commit | Line | Data |
---|---|---|
85aaf69f | 1 | % Error Handling |
1a4d82fc | 2 | |
85aaf69f | 3 | > The best-laid plans of mice and men |
1a4d82fc JJ |
4 | > Often go awry |
5 | > | |
6 | > "Tae a Moose", Robert Burns | |
7 | ||
8 | Sometimes, things just go wrong. It's important to have a plan for when the | |
9 | inevitable happens. Rust has rich support for handling errors that may (let's | |
10 | be honest: will) occur in your programs. | |
11 | ||
12 | There are two main kinds of errors that can occur in your programs: failures, | |
13 | and panics. Let's talk about the difference between the two, and then discuss | |
14 | how to handle each. Then, we'll discuss upgrading failures to panics. | |
15 | ||
16 | # Failure vs. Panic | |
17 | ||
18 | Rust uses two terms to differentiate between two forms of error: failure, and | |
85aaf69f SL |
19 | panic. A *failure* is an error that can be recovered from in some way. A |
20 | *panic* is an error that cannot be recovered from. | |
1a4d82fc | 21 | |
85aaf69f | 22 | What do we mean by "recover"? Well, in most cases, the possibility of an error |
bd371182 | 23 | is expected. For example, consider the `parse` function: |
1a4d82fc | 24 | |
bd371182 AL |
25 | ```ignore |
26 | "5".parse(); | |
1a4d82fc JJ |
27 | ``` |
28 | ||
bd371182 AL |
29 | This method converts a string into another type. But because it's a string, you |
30 | can't be sure that the conversion actually works. For example, what should this | |
31 | convert to? | |
1a4d82fc | 32 | |
bd371182 AL |
33 | ```ignore |
34 | "hello5world".parse(); | |
1a4d82fc JJ |
35 | ``` |
36 | ||
37 | This won't work. So we know that this function will only work properly for some | |
85aaf69f | 38 | inputs. It's expected behavior. We call this kind of error a *failure*. |
1a4d82fc JJ |
39 | |
40 | On the other hand, sometimes, there are errors that are unexpected, or which | |
41 | we cannot recover from. A classic example is an `assert!`: | |
42 | ||
bd371182 AL |
43 | ```rust |
44 | # let x = 5; | |
1a4d82fc JJ |
45 | assert!(x == 5); |
46 | ``` | |
47 | ||
48 | We use `assert!` to declare that something is true. If it's not true, something | |
49 | is very wrong. Wrong enough that we can't continue with things in the current | |
85aaf69f | 50 | state. Another example is using the `unreachable!()` macro: |
1a4d82fc | 51 | |
62682a34 | 52 | ```rust,ignore |
1a4d82fc JJ |
53 | enum Event { |
54 | NewRelease, | |
55 | } | |
56 | ||
57 | fn probability(_: &Event) -> f64 { | |
58 | // real implementation would be more complex, of course | |
59 | 0.95 | |
60 | } | |
61 | ||
62 | fn descriptive_probability(event: Event) -> &'static str { | |
63 | match probability(&event) { | |
85aaf69f SL |
64 | 1.00 => "certain", |
65 | 0.00 => "impossible", | |
1a4d82fc JJ |
66 | 0.00 ... 0.25 => "very unlikely", |
67 | 0.25 ... 0.50 => "unlikely", | |
68 | 0.50 ... 0.75 => "likely", | |
85aaf69f | 69 | 0.75 ... 1.00 => "very likely", |
1a4d82fc JJ |
70 | } |
71 | } | |
72 | ||
73 | fn main() { | |
74 | std::io::println(descriptive_probability(NewRelease)); | |
75 | } | |
76 | ``` | |
77 | ||
78 | This will give us an error: | |
79 | ||
80 | ```text | |
81 | error: non-exhaustive patterns: `_` not covered [E0004] | |
82 | ``` | |
83 | ||
84 | While we know that we've covered all possible cases, Rust can't tell. It | |
85 | doesn't know that probability is between 0.0 and 1.0. So we add another case: | |
86 | ||
87 | ```rust | |
88 | use Event::NewRelease; | |
89 | ||
90 | enum Event { | |
91 | NewRelease, | |
92 | } | |
93 | ||
94 | fn probability(_: &Event) -> f64 { | |
95 | // real implementation would be more complex, of course | |
96 | 0.95 | |
97 | } | |
98 | ||
99 | fn descriptive_probability(event: Event) -> &'static str { | |
100 | match probability(&event) { | |
85aaf69f SL |
101 | 1.00 => "certain", |
102 | 0.00 => "impossible", | |
1a4d82fc JJ |
103 | 0.00 ... 0.25 => "very unlikely", |
104 | 0.25 ... 0.50 => "unlikely", | |
105 | 0.50 ... 0.75 => "likely", | |
85aaf69f | 106 | 0.75 ... 1.00 => "very likely", |
1a4d82fc JJ |
107 | _ => unreachable!() |
108 | } | |
109 | } | |
110 | ||
111 | fn main() { | |
112 | println!("{}", descriptive_probability(NewRelease)); | |
113 | } | |
114 | ``` | |
115 | ||
116 | We shouldn't ever hit the `_` case, so we use the `unreachable!()` macro to | |
117 | indicate this. `unreachable!()` gives a different kind of error than `Result`. | |
85aaf69f | 118 | Rust calls these sorts of errors *panics*. |
1a4d82fc JJ |
119 | |
120 | # Handling errors with `Option` and `Result` | |
121 | ||
122 | The simplest way to indicate that a function may fail is to use the `Option<T>` | |
bd371182 AL |
123 | type. For example, the `find` method on strings attempts to find a pattern |
124 | in a string, and returns an `Option`: | |
1a4d82fc | 125 | |
bd371182 AL |
126 | ```rust |
127 | let s = "foo"; | |
128 | ||
129 | assert_eq!(s.find('f'), Some(0)); | |
130 | assert_eq!(s.find('z'), None); | |
1a4d82fc JJ |
131 | ``` |
132 | ||
1a4d82fc JJ |
133 | |
134 | This is appropriate for the simplest of cases, but doesn't give us a lot of | |
bd371182 | 135 | information in the failure case. What if we wanted to know _why_ the function |
1a4d82fc JJ |
136 | failed? For this, we can use the `Result<T, E>` type. It looks like this: |
137 | ||
138 | ```rust | |
139 | enum Result<T, E> { | |
140 | Ok(T), | |
141 | Err(E) | |
142 | } | |
143 | ``` | |
144 | ||
145 | This enum is provided by Rust itself, so you don't need to define it to use it | |
146 | in your code. The `Ok(T)` variant represents a success, and the `Err(E)` variant | |
147 | represents a failure. Returning a `Result` instead of an `Option` is recommended | |
148 | for all but the most trivial of situations. | |
149 | ||
150 | Here's an example of using `Result`: | |
151 | ||
152 | ```rust | |
85aaf69f | 153 | #[derive(Debug)] |
1a4d82fc JJ |
154 | enum Version { Version1, Version2 } |
155 | ||
85aaf69f | 156 | #[derive(Debug)] |
1a4d82fc JJ |
157 | enum ParseError { InvalidHeaderLength, InvalidVersion } |
158 | ||
159 | fn parse_version(header: &[u8]) -> Result<Version, ParseError> { | |
160 | if header.len() < 1 { | |
161 | return Err(ParseError::InvalidHeaderLength); | |
162 | } | |
163 | match header[0] { | |
164 | 1 => Ok(Version::Version1), | |
165 | 2 => Ok(Version::Version2), | |
166 | _ => Err(ParseError::InvalidVersion) | |
167 | } | |
168 | } | |
169 | ||
170 | let version = parse_version(&[1, 2, 3, 4]); | |
171 | match version { | |
172 | Ok(v) => { | |
173 | println!("working with version: {:?}", v); | |
174 | } | |
175 | Err(e) => { | |
176 | println!("error parsing header: {:?}", e); | |
177 | } | |
178 | } | |
179 | ``` | |
180 | ||
181 | This function makes use of an enum, `ParseError`, to enumerate the various | |
182 | errors that can occur. | |
183 | ||
d9579d0f AL |
184 | The [`Debug`](../std/fmt/trait.Debug.html) trait is what lets us print the enum value using the `{:?}` format operation. |
185 | ||
1a4d82fc JJ |
186 | # Non-recoverable errors with `panic!` |
187 | ||
188 | In the case of an error that is unexpected and not recoverable, the `panic!` | |
85aaf69f | 189 | macro will induce a panic. This will crash the current thread, and give an error: |
1a4d82fc | 190 | |
62682a34 | 191 | ```rust,ignore |
1a4d82fc JJ |
192 | panic!("boom"); |
193 | ``` | |
194 | ||
195 | gives | |
196 | ||
197 | ```text | |
85aaf69f | 198 | thread '<main>' panicked at 'boom', hello.rs:2 |
1a4d82fc JJ |
199 | ``` |
200 | ||
201 | when you run it. | |
202 | ||
203 | Because these kinds of situations are relatively rare, use panics sparingly. | |
204 | ||
205 | # Upgrading failures to panics | |
206 | ||
207 | In certain circumstances, even though a function may fail, we may want to treat | |
c34b1796 | 208 | it as a panic instead. For example, `io::stdin().read_line(&mut buffer)` returns |
bd371182 | 209 | a `Result<usize>`, when there is an error reading the line. This allows us to |
c34b1796 | 210 | handle and possibly recover from error. |
1a4d82fc JJ |
211 | |
212 | If we don't want to handle this error, and would rather just abort the program, | |
213 | we can use the `unwrap()` method: | |
214 | ||
62682a34 | 215 | ```rust,ignore |
c34b1796 | 216 | io::stdin().read_line(&mut buffer).unwrap(); |
1a4d82fc JJ |
217 | ``` |
218 | ||
bd371182 | 219 | `unwrap()` will `panic!` if the `Result` is `Err`. This basically says "Give |
1a4d82fc JJ |
220 | me the value, and if something goes wrong, just crash." This is less reliable |
221 | than matching the error and attempting to recover, but is also significantly | |
222 | shorter. Sometimes, just crashing is appropriate. | |
223 | ||
224 | There's another way of doing this that's a bit nicer than `unwrap()`: | |
225 | ||
62682a34 | 226 | ```rust,ignore |
c34b1796 | 227 | let mut buffer = String::new(); |
62682a34 SL |
228 | let num_bytes_read = io::stdin().read_line(&mut buffer) |
229 | .ok() | |
230 | .expect("Failed to read line"); | |
1a4d82fc | 231 | ``` |
c34b1796 AL |
232 | |
233 | `ok()` converts the `Result` into an `Option`, and `expect()` does the same | |
1a4d82fc JJ |
234 | thing as `unwrap()`, but takes a message. This message is passed along to the |
235 | underlying `panic!`, providing a better error message if the code errors. | |
c34b1796 AL |
236 | |
237 | # Using `try!` | |
238 | ||
239 | When writing code that calls many functions that return the `Result` type, the | |
240 | error handling can be tedious. The `try!` macro hides some of the boilerplate | |
241 | of propagating errors up the call stack. | |
242 | ||
243 | It replaces this: | |
244 | ||
245 | ```rust | |
246 | use std::fs::File; | |
247 | use std::io; | |
248 | use std::io::prelude::*; | |
249 | ||
250 | struct Info { | |
251 | name: String, | |
252 | age: i32, | |
253 | rating: i32, | |
254 | } | |
255 | ||
256 | fn write_info(info: &Info) -> io::Result<()> { | |
bd371182 | 257 | let mut file = File::create("my_best_friends.txt").unwrap(); |
c34b1796 AL |
258 | |
259 | if let Err(e) = writeln!(&mut file, "name: {}", info.name) { | |
260 | return Err(e) | |
261 | } | |
262 | if let Err(e) = writeln!(&mut file, "age: {}", info.age) { | |
263 | return Err(e) | |
264 | } | |
265 | if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) { | |
266 | return Err(e) | |
267 | } | |
268 | ||
269 | return Ok(()); | |
270 | } | |
271 | ``` | |
272 | ||
273 | With this: | |
274 | ||
275 | ```rust | |
276 | use std::fs::File; | |
277 | use std::io; | |
278 | use std::io::prelude::*; | |
279 | ||
280 | struct Info { | |
281 | name: String, | |
282 | age: i32, | |
283 | rating: i32, | |
284 | } | |
285 | ||
286 | fn write_info(info: &Info) -> io::Result<()> { | |
62682a34 | 287 | let mut file = File::create("my_best_friends.txt").unwrap(); |
c34b1796 AL |
288 | |
289 | try!(writeln!(&mut file, "name: {}", info.name)); | |
290 | try!(writeln!(&mut file, "age: {}", info.age)); | |
291 | try!(writeln!(&mut file, "rating: {}", info.rating)); | |
292 | ||
293 | return Ok(()); | |
294 | } | |
295 | ``` | |
296 | ||
297 | Wrapping an expression in `try!` will result in the unwrapped success (`Ok`) | |
298 | value, unless the result is `Err`, in which case `Err` is returned early from | |
299 | the enclosing function. | |
300 | ||
301 | It's worth noting that you can only use `try!` from a function that returns a | |
302 | `Result`, which means that you cannot use `try!` inside of `main()`, because | |
303 | `main()` doesn't return anything. | |
304 | ||
bd371182 | 305 | `try!` makes use of [`From<Error>`](../std/convert/trait.From.html) to determine |
c34b1796 | 306 | what to return in the error case. |