Errors are a fact of life in software development. No matter how carefully we write our code, there will always be the possibility of something going wrong. This is why it is important to have a good error handling strategy in place.
Rust groups errors into two major categories: recoverable and unrecoverable errors. Recoverable errors are those that we can recover from and continue executing our program. Unrecoverable errors are those that indicate a serious problem with our program, and we should stop execution immediately.
Recoverable Errors
Rust uses the Result<T, E>
type to represent recoverable errors. The T
type parameter represents the type of value that will be returned if the operation is successful. The E
type parameter represents the type of error that can be returned if the operation fails.
For example, the following function might try to read a file from disk:
fn read_file(filename: &str) -> Result<String, std::io::Error> {
// ...
}
This function returns a Result
value. If the function is able to read the file successfully, it will return a Result::Ok
value containing the contents of the file. If the function fails to read the file, it will return a Result::Err
value containing an error object.
We can handle the Result
value returned by the read_file
function in a few different ways. One way is to use the match
expression:
match read_file("myfile.txt") {
Ok(contents) => {
// The file was read successfully.
// Do something with the contents of the file.
}
Err(error) => {
// The file failed to read.
// Handle the error.
}
}
Another way to handle the Result
value is to use the ?
operator. The ?
operator will unpack the Result
value and return the value inside the Ok
variant if it exists. If the Result
value is an Err
variant, the ?
operator will return the Err
value and propagate the error to the calling function.
For example, the following code shows how to use the ?
operator to read a file from disk and print its contents to the console:
fn main() {
let contents = read_file("myfile.txt")?;
println!("{}", contents);
}
If the read_file
function fails to read the file, the ?
operator will propagate the error to the main
function and the program will terminate.
Unrecoverable Errors
To handle unrecoverable errors in Rust, we use the panic!
macro. The panic!
macro takes a message as an argument and prints it to the console before terminating the program.
For example, the following code shows how to use the panic!
macro to handle an unrecoverable error:
fn main() {
if some_condition {
// This is an unrecoverable error.
panic!("Something went wrong!");
}
// ...
}
If the condition in the if
statement is met, the program will terminate and print the message "Something went wrong!" to the console.
Choosing Between Recoverable and Unrecoverable Errors
When deciding whether to handle an error as recoverable or unrecoverable, we should consider the following:
- Is it possible to recover from the error? If the error is something like a file not found error, then it is likely that we can recover from the error by retrying the operation or asking the user for a different file. However, if the error is something like trying to access a location beyond the end of an array, then it is not possible to recover from the error and we should stop execution immediately.
- What are the consequences of failing to handle the error? If failing to handle the error would cause the program to crash or lose data, then we should handle the error as unrecoverable. However, if failing to handle the error would simply cause the program to be less convenient to use, then we might choose to handle the error as recoverable.