30 days of Rust - Day Eight - Error Handling
If the world was perfect, code would always run with no problems. But unfortunaltely, that's not the case and we need a way to deal with it. We can either CRASH the program or handle it in a more appropriate way.
When building software, we need to anticipate the possibility of a failure and how we work with it.
Day 8 - Error Handling in Rust
Rust is quite unique in the way it handles errors. In many programming languages such as Java, you only have one type of Error/ Exception. Rust on the other hand differentiates them into 2 groups, namely recoverable and unrecoverable errors.
Unrecoverable errors
The easiest way to deal with errors is to use the panic!
macro. It simply stops running the program immediately.
fn main() {
panic!("All good things come to an end!");
}
Generally we want to use panic only when there's no other way to deal with the error.
An example of when Rust itself throws a panic
is when we try to get an element with an index which is greater than the length of a list as we have seen with Vectors.
thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 70'
Recoverable errors
Most of the time, we can anticipate an error and deal with it without having to terminate the program.
The Option we looked at on day 4 allows us to have a value which may be non-existent.
Rust has a Result enum for returning and propagating errors. It is defined as;
enum Result<T, E>{
Ok(T),
Err(E),
}
If the operation is successful, the value is wrapped in OK(value)
, else the error is wrapped in Err(E)
.
The result enum is primarily used for input/output tasks such as parsing strings to other types and accessing files.
It's best suited for when we expect failures. I
If you remember the temperature converter program I created on day one, we had the .expect(msg)
method which returns a Result<T, E>
.
Example
Let's say you and I have created a new music player, a competitor to Spotify because we're innovative 📱. When a user clicks on a song, we call the play_song
function to read a song from our device.
fn play_song(song_name:String){
let song = File::open(song_name);
let song = match song {
Ok(file) => file,
Err(error) => match error.kind(){
ErrorKind::NotFound => match File::download(song_name){
Ok(fc) => fc ,
Err(e) => panic!("Failed to download the song: {:?}", e),
},
other_error => {
panic!("Failed to open the song: {:?}", other_error)
}
},
}
}
The code above opens a song by using its name. Now there's a chance that the song does not exist in our files. What happens thereafter? Do we stop the program or do we go and download it from the internet and then play the song?
You guessed correctly, we then head over to our server and download the song. This too might fail, for a number of reasons. e.g The song is not available in your country or the internet is down.
ErrorKind::NotFound => match File::download(song_name){
Ok(fc) => fc ,
Err(e) => panic!("Failed to download the song: {:?}", e),
}
We only then let our program panic
after we tried to open and then download the song with no luck.
To recover or not?
For the most part, you should always recover from errors by returning the Result
instead of throwing a panic
. This allows the code which is calling our function to handle the error in whatever way they feel like.
Conclusion
Rust has a wide range of error handling ways and concepts which allow us to create more reliable and safe software. We can always choose to recover from an error or entirely terminate the process.
Thank you for taking time out of your day to learn Rust with me. Looking forward to building some amazing software by the end of this 30 day series. Please recommend a project that you would like me to try out.
Until next time (tomorrow)😝. Take care.