Errors are a part of Software, and Rust has features to terminate an application using the panic macro and handle failures gracefully with the Result enum. These Rust’s features are similar to the concept of exception handling in other programming languages. There are two types of errors – recoverable and unrecoverable. A recoverable error is typically an anticipated problem. On the other hand, an unrecoverable error is something that disrupts and terminates a program abnormally.
Highlights
These are the highlights of this post.
- Generate recoverable errors using the panic macro.
- Generate recoverable error due to the out-of-bounds Vec index.
- Use profile.release and profile.dev with panic = ‘abort’ to minimize the verbosity of recoverable errors.
- Generate recoverable errors from within a function using the Result enum and its variants.
- Use the BACKTRACE environment variable.
- Use the ?operator in a function that returns Result.
Unrecoverable Errors Using Panic
To create an unrecoverable error, we use the panic!macro.
1 2 3 | fn main() { panic!("Unrecoverable Error Detected! Terminating program..."); } |
The error terminates the program with the following message.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | C:/Users/karldev/.cargo/bin/cargo.exe run --color=always --package error_handle --bin error_handle Compiling error_handle v0.1.0 (C:\Users\karldev\Desktop\dev\blogs\rust\error_handle) Finished dev [unoptimized + debuginfo] target(s) in 0.70s Running `target\debug\error_handle.exe` thread 'main' panicked at 'Unrecoverable Error Detected! Terminating program...', src\main.rs:2:5 stack backtrace: 0: backtrace::backtrace::trace_unsynchronized at C:\Users\VssAdministrator\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.40\src\backtrace\mod.rs:66 1: std::sys_common::backtrace::_print_fmt at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\sys_common\backtrace.rs:77 2: std::sys_common::backtrace::_print::{{impl}}::fmt at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\sys_common\backtrace.rs:61 3: core::fmt::write at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libcore\fmt\mod.rs:1028 4: std::io::Write::write_fmt<std::sys::windows::stdio::Stderr> at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\io\mod.rs:1412 5: std::sys_common::backtrace::_print at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\sys_common\backtrace.rs:65 6: std::sys_common::backtrace::print at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\sys_common\backtrace.rs:50 7: std::panicking::default_hook::{{closure}} at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\panicking.rs:188 8: std::panicking::default_hook at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\panicking.rs:205 9: std::panicking::rust_panic_with_hook at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\/src\libstd\panicking.rs:464 10: std::panicking::begin_panic<str*> at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\src\libstd\panicking.rs:400 ... 22: __scrt_common_main_seh at d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 23: BaseThreadInitThunk 24: RtlUserThreadStart note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. error: process didn't exit successfully: `target\debug\error_handle.exe` (exit code: 101) |
This error message is too lengthy, and we may not want this, especially on release versions, when we use the panic macro. We can cut this short using panic = 'abort' in Cargo.toml both for release and unoptimized versions.
1 2 3 4 5 6 7 8 9 10 11 12 13 | [package] name = "error_handle" version = "0.1.0" authors = ["Karl San Gabriel"] edition = "2018" [profile.release] panic = 'abort' [profile.dev] panic = 'abort' [dependencies] |
For instance, when we are building an unoptimized binary.
1 2 3 4 5 6 7 8 9 | C:\Users\karldev\Desktop\dev\blogs\rust\error_handle>cargo build Finished dev [unoptimized + debuginfo] target(s) in 0.02s C:\Users\karldev\Desktop\dev\blogs\rust\error_handle>cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target\debug\error_handle.exe` thread 'main' panicked at 'Unrecoverable Error Detected! Terminating program...', src\main.rs:2:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. error: process didn't exit successfully: `target\debug\error_handle.exe` (exit code: 0xc000001d, STATUS_ILLEGAL_INSTRUCTION) |
On the other hand, when we are building a release binary.
1 2 3 4 5 6 7 8 9 | C:\Users\karldev\Desktop\dev\blogs\rust\error_handle>cargo build --release Finished release [optimized] target(s) in 0.02s C:\Users\karldev\Desktop\dev\blogs\rust\error_handle>cargo run --release Finished release [optimized] target(s) in 0.02s Running `target\release\error_handle.exe` thread 'main' panicked at 'Unrecoverable Error Detected! Terminating program...', src\main.rs:2:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. error: process didn't exit successfully: `target\release\error_handle.exe` (exit code: 0xc000001d, STATUS_ILLEGAL_INSTRUCTION) |
We can also let Rust create unrecoverable errors when encountering incorrect codes trying to access something illegal, e.g., access out-of-bounds array index.
1 2 3 4 5 6 7 8 9 10 11 | fn main() { let list = vec![1, 2, 3]; println!("{}", list[0]); println!("{}", list[1]); println!("{}", list[2]); // Panic here! println!("{}", list[3]); } |
When we run these codes, we get the following error.
1 2 3 4 5 6 7 8 9 10 | C:\Users\karldev\Desktop\dev\blogs\rust\error_handle>cargo run --release Compiling error_handle v0.1.0 (C:\Users\karldev\Desktop\dev\blogs\rust\error_handle) Finished release [optimized] target(s) in 0.58s Running `target\release\error_handle.exe` 1 2 3 thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 3', /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14\src\libcore\slice\mod.rs:2796:10 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. error: process didn't exit successfully: `target\release\error_handle.exe` (exit code: 0xc000001d, STATUS_ILLEGAL_INSTRUCTION) |
The Result Enum And How To Handle Recoverable Errors
First, how do we generate recoverable errors? With unrecoverable errors, we use the panic! macro. Consider the following function.
1 2 3 4 5 6 7 8 9 10 11 12 | use std::io::Error; //use std::io::Result; // We can also use std::io::Result<T> fn to_throw_error_or_not(option: i32) -> Result<String, Error> { if option == 1 { Ok(String::from("Turreta loves Rust!")) } else { Err(std::io::Error::new(std::io::ErrorKind::Other, "Error!")) } } |
To generate a recoverable error from a function, we need to use the Result enum and its variants – Ok and Err. Now, Our function returns the String value “Turreta loves Rust!” when the option is 1; otherwise, it returns a recoverable error.
Next, how do we use this function?
1 2 3 4 5 6 7 8 | let result_str: Result<String, Error> = to_throw_error_or_not(1); let mut f = match result_str { Ok(str_val) => str_val, Err(e) => "There was an error!".to_string(), }; println!("{}", f); |
This outputs:
1 | Value was Turreta loves Rust! |
If we pass 2 to the function, we will get the following:
1 | There was an error! |
Using the match expression could make our codes bloated. We could use the ?operator. When using this operator, the function call must be another function that returns Result<T, E>. The other method now has to handle the errors. The new codes are as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | use std::fs::File; use std::error::Error; fn to_throw_error_or_not(option: i32) -> Result<String, std::io::Error>{ if option == 1 { Ok(String::from("Turreta loves Rust!")) } else { Err(std::io::Error::new(std::io::ErrorKind::Other, "Error!")) } } fn main() -> Result<(), Box<dyn Error>>{ let result_str = to_throw_error_or_not(2)?; println!("{}", result_str); Ok(()) } |
Notice we modified the main function to return Result so that we can use the ?operator within it. The Box<dyn Error> is needed to avoid the E0277 error.
1 | error[E0277]: the size for values of type `(dyn std::error::Error + 'static)` cannot be known at compilation time |
Tested panic and Result to handle errors with Rust 1.40.0.
This post is now part of the Rust Programming Language For Beginners Tutorial.