Some function we implement might fail. Either because the caller did not supply the correct parameter or some other issue. How can we indicate failure? In some languages failure is indicated by the raising (throwing) of exceptions. In other languages the function can return some value (e.g. null, undef etc.) to indicate failure.
In Rust a function that might need to report an error should return a value of Result type. In general a Result
can have either a value wrapped in Ok()
or a value wrapped in Err()
. What kind of values are wrapped in either of those is up to the author of the function.
In the first example we have a function that expects an integer number between -100 and +100 and then checks if the number is smaller or bigger or exactly the same
as the hidden_number
. This might be part of a number guessing game where the hidden number was generated by a random generator. However now we would like to focus
on the error reporting part.
The guess
function return Result<&'static str, &'static str>
. The generic description of Result is Result<T, E>
so the first type is the type of the successful result
the second type is that of the error. In our case they are both static
strings that are baked in the binary at compilation time.
In case the user supplied a number that is out of range we return a static string wrapped in the Err()
call.
In case the number was in range we supply our message wrapped in an Ok()
call.
The caller will receive a value of Result
type and it has to be handled somehow. There are plenty of ways to handle it. For example calling unwrap
will panic!
if the result contained something
wrapped in Err
and will return the content of the Ok()
if the result contained a value wrapped in Ok()
. This is mostly used in tests.
The caller can also use match
to handle both the Ok()
and the Err()
case with additional code.
See the functions associated with Result enum to find more ways to handle the errors.
examples/return-result-str/src/main.rs
fn main() {
let res = guess(42);
assert_eq!(res, Ok("hit"));
assert_eq!(res.unwrap(), "hit");
let res = guess(50);
assert!(res.is_ok());
assert_eq!(res, Ok("Too big"));
let res = guess(0);
assert!(res.is_ok());
assert_eq!(res, Ok("Too small"));
assert!(guess(150).is_err());
assert_eq!(guess(150), Err("Out of range: too big"));
assert!(guess(-150).is_err());
assert_eq!(guess(-150), Err("Out of range: too small"));
}
fn guess(num: i32) -> Result<&'static str, &'static str> {
let hidden_number = 42;
if num < -100 {
return Err("Out of range: too small");
}
if num > 100 {
return Err("Out of range: too big");
}
if num < hidden_number {
return Ok("Too small");
}
if num > hidden_number {
return Ok("Too big");
}
Ok("hit")
}
Returning a String generated on the fly
We might want to be more precise in our error message and even include the number we received. In this case we need to generate the error message
while the program is already running so we cannot return a static
string wrapped in Err()
. Therefore we change the second type in the returning Result
to be String
.
This has some impact on how we generate the error message using the format!
macro and how we use String::from
when we would like to verify the results.
examples/return-result-string/src/main.rs
fn main() {
let res = guess(42);
assert_eq!(res, Ok("hit"));
assert_eq!(res.unwrap(), "hit");
let res = guess(50);
assert!(res.is_ok());
assert_eq!(res, Ok("Too big"));
let res = guess(0);
assert!(res.is_ok());
assert_eq!(res, Ok("Too small"));
assert!(guess(150).is_err());
assert_eq!(guess(150), Err(String::from("Out of range: 150 is too big")));
assert!(guess(-150).is_err());
assert_eq!(guess(-150), Err(String::from("Out of range: -150 is too small")));
}
fn guess(num: i32) -> Result<&'static str, String> {
let hidden_number = 42;
if num < -100 {
return Err(format!("Out of range: {num} is too small"));
}
if num > 100 {
return Err(format!("Out of range: {num} is too big"));
}
if num < hidden_number {
return Ok("Too small");
}
if num > hidden_number {
return Ok("Too big");
}
Ok("hit")
}
Notes
There are many more types we can return as part of a Result
enum.
There are plenty more ways to handle the Result
by the caller.
It just occurred to me that including the valid range in the error message would be even more useful. I'll leave it as an exercise to the reader.