PageRenderTime 54ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/doc/trpl/error-handling.md

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