Expand description
Error handling with theResult type.
Result<T, E> is the type used for returning and propagatingerrors. It is an enum with the variants,Ok(T), representingsuccess and containing a value, andErr(E), representing errorand containing an error value.
Functions returnResult whenever errors are expected andrecoverable. In thestd crate,Result is most prominently usedforI/O.
A simple function returningResult might bedefined and used like so:
#[derive(Debug)]enumVersion { Version1, Version2 }fnparse_version(header:&[u8]) ->Result<Version,&'staticstr> {matchheader.get(0) {None=>Err("invalid header length"),Some(&1) =>Ok(Version::Version1),Some(&2) =>Ok(Version::Version2),Some(_) =>Err("invalid version"), }}letversion = parse_version(&[1,2,3,4]);matchversion {Ok(v) =>println!("working with version: {v:?}"),Err(e) =>println!("error parsing header: {e:?}"),}Pattern matching onResults is clear and straightforward forsimple cases, butResult comes with some convenience methodsthat make working with it more succinct.
// The `is_ok` and `is_err` methods do what they say.letgood_result:Result<i32, i32> =Ok(10);letbad_result:Result<i32, i32> =Err(10);assert!(good_result.is_ok() && !good_result.is_err());assert!(bad_result.is_err() && !bad_result.is_ok());// `map` and `map_err` consume the `Result` and produce another.letgood_result:Result<i32, i32> = good_result.map(|i| i +1);letbad_result:Result<i32, i32> = bad_result.map_err(|i| i -1);assert_eq!(good_result,Ok(11));assert_eq!(bad_result,Err(9));// Use `and_then` to continue the computation.letgood_result:Result<bool, i32> = good_result.and_then(|i|Ok(i ==11));assert_eq!(good_result,Ok(true));// Use `or_else` to handle the error.letbad_result:Result<i32, i32> = bad_result.or_else(|i|Ok(i +20));assert_eq!(bad_result,Ok(29));// Consume the result and return the contents with `unwrap`.letfinal_awesome_result = good_result.unwrap();assert!(final_awesome_result)§Results must be used
A common problem with using return values to indicate errors isthat it is easy to ignore the return value, thus failing to handlethe error.Result is annotated with the#[must_use] attribute,which will cause the compiler to issue a warning when a Resultvalue is ignored. This makesResult especially useful withfunctions that may encounter errors but don’t otherwise return auseful value.
Consider thewrite_all method defined for I/O typesby theWrite trait:
Note: The actual definition ofWrite usesio::Result, whichis just a synonym forResult<T,io::Error>.
This method doesn’t produce a value, but the write mayfail. It’s crucial to handle the error case, andnot writesomething like this:
usestd::fs::File;usestd::io::prelude::*;letmutfile = File::create("valuable_data.txt").unwrap();// If `write_all` errors, then we'll never know, because the return// value is ignored.file.write_all(b"important message");If youdo write that in Rust, the compiler will give you awarning (by default, controlled by theunused_must_use lint).
You might instead, if you don’t want to handle the error, simplyassert success withexpect. This will panic if thewrite fails, providing a marginally useful message indicating why:
usestd::fs::File;usestd::io::prelude::*;letmutfile = File::create("valuable_data.txt").unwrap();file.write_all(b"important message").expect("failed to write message");You might also simply assert success:
Or propagate the error up the call stack with?:
fnwrite_message() -> io::Result<()> {letmutfile = File::create("valuable_data.txt")?; file.write_all(b"important message")?;Ok(())}§The question mark operator,?
When writing code that calls many functions that return theResult type, the error handling can be tedious. The question markoperator,?, hides some of the boilerplate of propagating errorsup the call stack.
It replaces this:
usestd::fs::File;usestd::io::prelude::*;usestd::io;structInfo { name: String, age: i32, rating: i32,}fnwrite_info(info:&Info) -> io::Result<()> {// Early return on errorletmutfile =matchFile::create("my_best_friends.txt") {Err(e) =>returnErr(e),Ok(f) => f, };if letErr(e) = file.write_all(format!("name: {}\n", info.name).as_bytes()) {returnErr(e) }if letErr(e) = file.write_all(format!("age: {}\n", info.age).as_bytes()) {returnErr(e) }if letErr(e) = file.write_all(format!("rating: {}\n", info.rating).as_bytes()) {returnErr(e) }Ok(())}With this:
usestd::fs::File;usestd::io::prelude::*;usestd::io;structInfo { name: String, age: i32, rating: i32,}fnwrite_info(info:&Info) -> io::Result<()> {letmutfile = File::create("my_best_friends.txt")?;// Early return on errorfile.write_all(format!("name: {}\n", info.name).as_bytes())?; file.write_all(format!("age: {}\n", info.age).as_bytes())?; file.write_all(format!("rating: {}\n", info.rating).as_bytes())?;Ok(())}It’s much nicer!
Ending the expression with? will result in theOk’s unwrapped value, unless the resultisErr, in which caseErr is returned early from the enclosing function.
? can be used in functions that returnResult because of theearly return ofErr that it provides.
§Representation
In some cases,Result<T, E> comes with size, alignment, and ABIguarantees. Specifically, one of either theT orE type must be a typethat qualifies for theOptionrepresentation guarantees (let’scall that typeI), and theother type is a zero-sized type withalignment 1 (a “1-ZST”).
If that is the case, thenResult<T, E> has the same size, alignment, andfunction call ABI asI (and therefore, asOption<I>). IfI isT,it is therefore sound to transmute a valuet of typeI to typeResult<T, E> (producing the valueOk(t)) and to transmute a valueOk(t) of typeResult<T, E> to typeI (producing the valuet). IfIisE, the same applies withOk replaced byErr.
For example,NonZeroI32 qualifies for theOption representationguarantees and() is a zero-sized type with alignment 1. This means thatbothResult<NonZeroI32, ()> andResult<(), NonZeroI32> have the samesize, alignment, and ABI asNonZeroI32 (andOption<NonZeroI32>). Theonly difference between these is in the implied semantics:
Option<NonZeroI32>is “a non-zero i32 might be present”Result<NonZeroI32, ()>is “a non-zero i32 success result, if any”Result<(), NonZeroI32>is “a non-zero i32 error result, if any”
§Method overview
In addition to working with pattern matching,Result provides awide variety of different methods.
§Querying the variant
Theis_ok andis_err methods returntrue if theResultisOk orErr, respectively.
Theis_ok_and andis_err_and methods apply the provided functionto the contents of theResult to produce a boolean value. If theResult does not have the expected variantthenfalse is returned instead without executing the function.
§Adapters for working with references
as_refconverts from&Result<T, E>toResult<&T, &E>as_mutconverts from&mut Result<T, E>toResult<&mut T, &mut E>as_derefconverts from&Result<T, E>toResult<&T::Target, &E>as_deref_mutconverts from&mut Result<T, E>toResult<&mut T::Target, &mut E>
§Extracting contained values
These methods extract the contained value in aResult<T, E> when itis theOk variant. If theResult isErr:
expectpanics with a provided custom messageunwrappanics with a generic messageunwrap_orreturns the provided default valueunwrap_or_defaultreturns the default value of the typeT(which must implement theDefaulttrait)unwrap_or_elsereturns the result of evaluating the providedfunctionunwrap_uncheckedproducesundefined behavior
The panicking methodsexpect andunwrap requireE toimplement theDebug trait.
These methods extract the contained value in aResult<T, E> when itis theErr variant. They requireT to implement theDebugtrait. If theResult isOk:
expect_errpanics with a provided custom messageunwrap_errpanics with a generic messageunwrap_err_uncheckedproducesundefined behavior
§Transforming contained values
These methods transformResult toOption:
errtransformsResult<T, E>intoOption<E>,mappingErr(e)toSome(e)andOk(v)toNoneoktransformsResult<T, E>intoOption<T>,mappingOk(v)toSome(v)andErr(e)toNonetransposetransposes aResultof anOptioninto anOptionof aResult
These methods transform the contained value of theOk variant:
maptransformsResult<T, E>intoResult<U, E>by applyingthe provided function to the contained value ofOkand leavingErrvalues unchangedinspecttakes ownership of theResult, applies theprovided function to the contained value by reference,and then returns theResult
These methods transform the contained value of theErr variant:
map_errtransformsResult<T, E>intoResult<T, F>byapplying the provided function to the contained value ofErrandleavingOkvalues unchangedinspect_errtakes ownership of theResult, applies theprovided function to the contained value ofErrby reference,and then returns theResult
These methods transform aResult<T, E> into a value of a possiblydifferent typeU:
map_orapplies the provided function to the contained value ofOk, or returns the provided default value if theResultisErrmap_or_elseapplies the provided function to the contained valueofOk, or applies the provided default fallback function to thecontained value ofErr
§Boolean operators
These methods treat theResult as a boolean value, whereOkacts liketrue andErr acts likefalse. There are twocategories of these methods: ones that take aResult as input, andones that take a function as input (to be lazily evaluated).
Theand andor methods take anotherResult as input, andproduce aResult as output. Theand method can produce aResult<U, E> value having a different inner typeU thanResult<T, E>. Theor method can produce aResult<T, F>value having a different error typeF thanResult<T, E>.
| method | self | input | output |
|---|---|---|---|
and | Err(e) | (ignored) | Err(e) |
and | Ok(x) | Err(d) | Err(d) |
and | Ok(x) | Ok(y) | Ok(y) |
or | Err(e) | Err(d) | Err(d) |
or | Err(e) | Ok(y) | Ok(y) |
or | Ok(x) | (ignored) | Ok(x) |
Theand_then andor_else methods take a function as input, andonly evaluate the function when they need to produce a new value. Theand_then method can produce aResult<U, E> value having adifferent inner typeU thanResult<T, E>. Theor_else methodcan produce aResult<T, F> value having a different error typeFthanResult<T, E>.
| method | self | function input | function result | output |
|---|---|---|---|---|
and_then | Err(e) | (not provided) | (not evaluated) | Err(e) |
and_then | Ok(x) | x | Err(d) | Err(d) |
and_then | Ok(x) | x | Ok(y) | Ok(y) |
or_else | Err(e) | e | Err(d) | Err(d) |
or_else | Err(e) | e | Ok(y) | Ok(y) |
or_else | Ok(x) | (not provided) | (not evaluated) | Ok(x) |
§Comparison operators
IfT andE both implementPartialOrd thenResult<T, E> willderive itsPartialOrd implementation. With this order, anOkcompares as less than anyErr, while twoOk or twoErrcompare as their contained values would inT orE respectively. IfTandE both also implementOrd, then so doesResult<T, E>.
assert!(Ok(1) <Err(0));letx:Result<i32, ()> =Ok(0);lety =Ok(1);assert!(x < y);letx:Result<(), i32> =Err(0);lety =Err(1);assert!(x < y);§Iterating overResult
AResult can be iterated over. This can be helpful if you need aniterator that is conditionally empty. The iterator will either producea single value (when theResult isOk), or produce no values(when theResult isErr). For example,into_iter acts likeonce(v) if theResult isOk(v), and likeempty() if theResult isErr.
Iterators overResult<T, E> come in three types:
into_iterconsumes theResultand produces the containedvalueiterproduces an immutable reference of type&Tto thecontained valueiter_mutproduces a mutable reference of type&mut Tto thecontained value
SeeIterating overOption for examples of how this can be useful.
You might want to use an iterator chain to do multiple instances of anoperation that can fail, but would like to ignore failures whilecontinuing to process the successful results. In this example, we takeadvantage of the iterable nature ofResult to select only theOk values usingflatten.
letmutresults =vec![];letmuterrs =vec![];letnums: Vec<_> = ["17","not a number","99","-27","768"] .into_iter() .map(u8::from_str)// Save clones of the raw `Result` values to inspect.inspect(|x| results.push(x.clone()))// Challenge: explain how this captures only the `Err` values.inspect(|x| errs.extend(x.clone().err())) .flatten() .collect();assert_eq!(errs.len(),3);assert_eq!(nums, [17,99]);println!("results {results:?}");println!("errs {errs:?}");println!("nums {nums:?}");§Collecting intoResult
Result implements theFromIterator trait,which allows an iterator overResult values to be collected into aResult of a collection of each contained value of the originalResult values, orErr if any of the elements wasErr.
letv = [Ok(2),Ok(4),Err("err!"),Ok(8)];letres:Result<Vec<_>,&str> = v.into_iter().collect();assert_eq!(res,Err("err!"));letv = [Ok(2),Ok(4),Ok(8)];letres:Result<Vec<_>,&str> = v.into_iter().collect();assert_eq!(res,Ok(vec![2,4,8]));Result also implements theProduct andSum traits, allowing an iterator overResult valuesto provide theproduct andsum methods.
Structs§
- Into
Iter - An iterator over the value in a
Okvariant of aResult. - Iter
- An iterator over a reference to the
Okvariant of aResult. - IterMut
- An iterator over a mutable reference to the
Okvariant of aResult.