Another way to obtain the same result is to wrap the dangerous read operation in a separate function, here the function read_u32() returns an Option type value to be tested in the main code:
// see code in Chapter 5/code/input_number2.rs use std::io;
fn main() { println!("Give an integer number bigger than 0:"); let num = read_u32(); match num { Some(val) => println!("That's the number: {}", val), None => println!("Failed to read number.") } } fn read_u32() -> Option<u32> { let mut buf = String::new(); if io::stdin().read_line(&mut buf).is_ok() { let result = buf.trim().parse::<u32>(); return match result { // (1) Ok(value) => Some(value), Err(_) => None //Failed to parse }; } None //Failed to read from stream }
In this code, we effectively transform a Result type value into an Option type value in the match as it can be seen in line (1).
Here is another example how we can use Result type to return an error condition and making a safe function for calculating the square root of a floating point number using the std::f32::sqrt() function:
// see code in Chapter 5/code/sqrt_match.rs fn sqroot(r: f32) -> Result<f32, String> { if r < 0.0 { return Err("Number cannot be negative!".to_string()); } Ok(f32::sqrt(r)) }
We guard against taking the square root of a negative number (which would give NAN--Not a Number) by returning an Err value.
let m = sqroot(42.0);
In the calling code, we use our trusted pattern match mechanism to distinguish between the two cases:
match m { Ok(sq) => println!("The square root of 42 is {}", sq), Err(str) => println!("{}", str) }
So, in the normal case, we get output as follows:
The square root of 42 is 6.480741
With let m = sqroot(-5.0); the error message is printed as follows:
Number cannot be negative!
Moving from a crash on error strategy to a strategy of actually handling the error means switching from ok().expect() or ok().unwrap() to a match statement that tests on the Ok(T) or Err(E) value outcome of the Result type value. You can still use the function unwrap() for quick prototyping.
Another way of handling a Result type value which shows more clearly why an errors occurs is as follows:
let result = match process_result_value { Err(why) => panic!("some error occurred: {}", Error::description(&why)), Ok(result) => result };
If the program could, in some way, recover from the error, we could decide not to stop it here, but just return to the calling function, like this:
let result = match process_result_value() { Err(why) => { println!("some error occurred: {}", Error::description(&why)); return; }, Ok(result) => result };
In many cases, you can also just test the outcome of the operation with the following:
if process_result_value().is_err() { // process error value }
This will return false in case of an error.
So, in Rust, errors are not exception objects like in other languages: that would not be possible because Rust has no virtual machine to catch exceptions. Rust uses a type-based approach to error-handling, instead of an exception being raised, an error is returned from a function in the Result type value, so that the developer can handle the error case.
Exceptions result in complicated control-flow and they don't work well with multithreaded code, Rust avoids this with its error-handling mechanism.