Result<T,E> in Rust
In my first puzzle in advent of code challenge I used some copy/pasted rust code which was not completely clear to me... It's time to understand the code and document it here.
I talking about .expect and .unwrap() methods.
File::open() will throw compilation error without .expect() in copy/pasted code
File::open(filename).expect("error message");
Parse function will throw compilation error without .unwrap() call in copy/pasted code
line.parse::<i32>().unwrap();
From signature of std::fs::File::open() I see that it returns Result<File> instead of File. Googling leads to Error Handling topic in Rust Book which explains:
Rust doesn’t have exceptions. Instead, it has the typeResult<T, E>
for recoverable errors and thepanic!
macro that stops execution when the program encounters an unrecoverable error.
Result<T, E>
is the type used for returning and propagating errors. It is an enum with the variants,Ok(T)
, representing success and containing a value, andErr(E)
, representing error and containing an error value.
Generic Result enum looks like this:
enum Result<T, E> { Ok(T), Err(E), }
After getting result developer should handle Ok(result) or Err(why) situations. Typical error handling in rust looks like this:
use std::fs::File; use std::error::Error; fn main() { let path = ".gitignore"; match File::open(&path) { Err(why) => panic!("couldn't open {}: {}", path, why.description()), Ok(file) => println!("{:?}", file), }; }
This is how rust book explains code above:
In the case whereFile::open
succeeds, the value in [match construction] will be an instance ofOk
that contains a file handle. In the case where it fails, the value in [match construction] will be an instance ofErr
that contains more information about the kind of error that happened.
Only one branch will be executed depending on operation result.
So what is purpose of .expect() in this code?
File::open(filename).expect("error message");
Looking at documentation for std::result::Result and seeing explanation:
Unwraps a result, yielding the content of anOk
. Panics if the value is anErr
, with a panic message including the passed message, and the content of theErr
.
So basically .expect() is a shorthand for match construction above. It either takes result or panics with message. I checked the code of std::result::Result and seeing this:
#[inline] #[stable(feature = "result_expect", since = "1.4.0")] pub fn expect(self, msg: &str) -> T { match self { Ok(t) => t, Err(e) => unwrap_failed(msg, e), } }
Very similar to example with Result matching i gave above.
Rust groups errors into two major categories: recoverable and unrecoverable errors. For a recoverable error, such as a file not found error, it’s reasonable to report the problem to the user and retry the operation. Unrecoverable errors are always symptoms of bugs, like trying to access a location beyond the end of an array.
.expect() makes unrecoverable error from recoverable. In simple cases it is okay to handle situation like this but production programs should not panic if file is missing or not readable.
.unwrap() is very similar to .expect:
Unwraps a result, yielding the content of anOk
. Panics if the value is anErr
, with a panic message provided by theErr
's value.
So difference from expect is only that expect takes additional error message.
Additional reading:
- Rust Book: Error Handling
- Rust By Example: Result
- std::result module documentation
- std::result::Result api