Introduction
Errors are common while writing Software. Rust assumes it beforehand, and hence provides robust mechanism to address it, if not prevent it.
Types Of Rust Errors
Rust categorizes errors into two kinds:
recoverable errors, in other words errors that can be recovered. In this case, you don't want to do much, you only want to address the issue, and possibly inform user to try once again.
unrecoverable errors, the serious kind of errors, likely bugs or vulnerabilities, where we want to stop the app!
Because we have two kinds of errors in Rust, there are also two ways to handle them.
type Result <T, E> handles recoverable errors.
panic! macro handles unrecoverable errors.
NOTE: In other Programming languages all errors are handled with Exceptions, because unlike Rust generally there is no distinction between the two kinds of errors. And so Rust has no Exceptions.
Panic!
This is how a Program will panic!
fn main() {
panic!("Panic and stop!");
}
Error:
thread 'main' panicked at src/main.rs:2:5:
Panic and stop!
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
This is a more practical case when a Program might panic!
fn main() {
println!("{}", divide(10, 0));
}
fn divide (a: i32, b: i32) -> i32 {
a / b
}
Error:
thread 'main' panicked at src/main.rs:6:5:
attempt to divide by zero
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Generally when a Program panics Rust cleans up all the memory associated with the Program and then closes. But it may take a lot of time. So we can also abort the Program immediately. In the Cargo.toml file we have to add this.
[profile.release]
panic = 'abort'
But this is generally not recommended unless absolutely required. Because later it becomes the job of the Operating System to clean up the mess.
Recoverable Errors
The friendly errors, that doesn't require the Program to panic!
And this is how they're generally represented, using generics.
enum Result<T, E> {
Ok(T),
Err(E),
}
The outcome is either a success or a failure. T is the success type, and E is the error type. The Ok and Err are just names, they can also be changed, the idea is one is success, the other is failure.
enum Result<T, E> {
Success(T),
Failure(E),
}
Below is a good example, when we may need Result.
use std::fs::File;
enum Result<T, E> {
Ok(T),
Err(E),
}
fn main() {
let file_found_result = File::open("hello.txt");
let file_found = match file_found_result {
Ok(file) => println!("File found. File: {:?}", file),
Err(error) => panic!("File not found! Error: {:?}", error),
};
}
Either the file will be present or no.
If present we're printing stuff about the file.
If not present we're panicking and closing with the error message.
Conclusion
There are also alternatives to using Result like using ErrorKind
We'll discuss all of it. For now that's all you need to know about basic Error handling. We'll be using it everywhere from now on, because writing buggy code is expected, wrongly handling bugs is what is unexpected!