Back to the basics - returning results
Previously we've used an Option<u8>
to return a valid age, or nothing. As you've seen, an Option
can either have
a Some(value)
or None
. Sometimes it is more useful, maybe even better, to return a specific value in the positive
case, and another value in an error condition.
As an alternative to Option
, Rust supports the Result
type. Where an Option
can only hold a single data type, a
Result
can return one of two possible data types. It has this signature:
Result< [good_type], [bad_type] >
The good_type
and bad_type
can be any valid Rust type.
In our case we will use this combination: Result<u8,String>
. This returns a u8
in the positive case, and a String
in case of an error. The values are specified with either Ok(value)
or Err(err_value)
. In our case we'll use:
Ok(u8)
or Err(String)
. We use a match
block to analyze the result.
This is what the full example would look like:
use std::str::FromStr; fn main() { println!("How old are you?"); match read_number() { Ok(age) => println!("You are {age} years old."), Err(err) => println!("{err}"), } } fn read_string() -> String { let mut input = String::new(); std::io::stdin() .read_line(&mut input) .expect("can not read user input"); input.trim().to_string() } fn read_number() -> Result<u8, String> { let input = read_string(); u8::from_str(&input).or(Err("You've entered an invalid age".to_string())) }
Let's look at the code.
The value in the .or(Err("You've entered an invalid age".to_string()))
method at the end of read_number()
will be
returned in case the from_str
conversion failed. When this happens, we are
returning Err("You've entered an invalid age".to_string())
As described at the beginning of this chapter, the Err(...)
method sets the [bad_type]
of the Result
. In our
case from_str
already takes care of setting the Ok(...)
in case the conversion was successful.
The match
block is very similar to the one we've used in the previous chapter, when we checked the returned Option
.
Only in this case it has two different arms: Ok(age)
and Err(err)
. Both age
and err
are local variables that get
set with the value from the 'good' or 'bad' response, respectively.
The program still behaves the same as in the previous chapter. However, in this case the error message to be printed is
set in the read_number()
function, not in main()
.
Exercise
Rewrite the error message to a more generic "You've entered an invalid number. Please enter a value between 0 and 255."
Now that we are returning a more generic error message, we can use the read_number()
function to request more numbers
than just age, and print the same error if needed.
Since we are returning the error message from the read_number()
function, we can now also distinguish between an
invalid input and no input.
use std::str::FromStr; fn main() { println!("How old are you?"); match read_number() { Ok(age) => println!("You are {age} years old."), Err(err) => println!("{err}"), } } fn read_string() -> String { let mut input = String::new(); std::io::stdin() .read_line(&mut input) .expect("can not read user input"); input.trim().to_string() } fn read_number() -> Result<u8, String> { let input = read_string(); if input.is_empty() { Err("You did not enter any data".to_string()) } else { u8::from_str(&input).or(Err("You've entered an invalid number".to_string())) } }
We test input.is_empty()
to check if the user did not enter any data. We use this in an if
block to differentiate
between no input and some input.
Notice the lack of
;
in the last five lines. This ensures that the result of theif
block is returned by theread_number()
function.