diff --git a/src/tutorial/errors.md b/src/tutorial/errors.md index b842a5ab..0eb610c8 100644 --- a/src/tutorial/errors.md +++ b/src/tutorial/errors.md @@ -1,5 +1,192 @@ # Nicer error reporting +We all can do nothing but accept the fact that errors will occur. +And in contrast to many other languages, +it's very hard not to notice and deal with this reality +when using Rust: +As it doesn't have exceptions, +all possible error states are often encoded in the return types of functions. + +## Results + +A function like [`read_to_string`] doesn't return a string. +Instead, it returns a [`Result`] +that contains either +a `String` +or an error of some type +(in this case [`std::io::Error`]). + +[`read_to_string`]: https://doc.rust-lang.org/1.27.2/std/fs/fn.read_to_string.html +[`Result`]: https://doc.rust-lang.org/1.27.2/std/result/index.html +[`std::io::Error`]: https://doc.rust-lang.org/1.27.2/std/io/type.Result.html + +How do you know which it is? +Since `Result` is an `enum`, +you can use `match` to check which variant it is: + +```rust +# fn main() -> Result<(), Box> { +let result = std::fs::read_to_string("test.txt"); +match result { + Ok(content) => { println!("File content: {}", content); } + Err(error) => { println!("Oh noes: {}", error); } +} +# } +``` + + + +## Unwrapping + +Now, we were able to access content of the file, +but we can't really do anything with it after the `match` block. +For this, we'll need to somehow deal with the error case. +The challenge is that all arms of a `match` block need to return something of the same type. +But there's a need trick to get around that: + +```rust +# fn main() -> Result<(), Box> { +let result = std::fs::read_to_string("test.txt"); +let content = match result { + Ok(content) => { content }, + Err(error) => { panic!("Can't deal with {}, just exit here", error); } +}; +println!("file content: {}", content); +# } +``` + +We can use the String in `content` after the match block. +If `result` were an error, the String wouldn't exist. +But since the program would exit before it ever reached a point where we use `content`, +it's fine. + +This may seem drastic, +but it's very convenient. +If your program needs to read that file and can't do anything if the file doesn't exist, +exiting is a valid strategy. +There's even a shortcut method on `Result`s, called `unwrap`: + +```rust +let content = std::fs::read_to_string("test.txt").unwrap(); +``` + +## No need to panic + +Of course, aborting the program is not the only way to deal with errors. +Instead of the `panic!`, we can also easily write `return`: + +```rust +# fn main() -> Result<(), Box> { +let result = std::fs::read_to_string("test.txt"); +let content = match result { + Ok(content) => { content }, + Err(error) => { return Err(error); } +}; +println!("file content: {}", content); +# Ok(()) +# } +``` + +This, however changes the return type our function needs. +Indeed, there was something hidden in our examples all this time: +The function signature this code lives in. +And in this last example with `return`, +it becomes important. +Here's the _full_ example: + +```rust +fn main() -> Result<(), Box> { + let result = std::fs::read_to_string("test.txt"); + let content = match result { + Ok(content) => { content }, + Err(error) => { return Err(error); } + }; + println!("file content: {}", content); + Ok(()) +} +``` + +Our return type is a `Result`! +This is why we can write `return Err(error);` in the second match arm. +See how there is an `Ok(())` at the bottom? +It's the default return value of the function and means +"Result is okay, as has no content". + + + +## Question Mark + +Just like calling `.unwrap()` is a shortcut +for the `match` with `panic!` in the error arm, +we have another shortcut for the `match` that `return`s in the error arm: +`?`. + +Thats's right, a question mark. +You can append this operator to a value of type `Result`, +and Rust will internally expand this to something very similar to +the `match` we just wrote. + +Give it a try: + +```rust +fn main() -> Result<(), Box> { + let content = std::fs::read_to_string("test.txt")?; + println!("file content: {}", content); + Ok(()) +} +``` + +Very concise! + + + +## Providing Context + +The errors you get when using `?` in your `main` function are okay, +but great they are not. +For example: +When you run `std::fs::read_to_string("test.txt")?` +but the file `test.txt` doesn't exist, +you get this output: + +> Error: Os { code: 2, kind: NotFound, message: "No such file or directory" } + +In cases where your code doesn't literally contain the file name, +it'd be very hard to tell which file was `NotFound`. +