5 Essential Ways to Handle Errors in Rust: A Comprehensive Guide
Learn five powerful error handling patterns in Rust, from basic panic handling to custom error types. Discover how Rust's trait system enables robust error management and compare it with Python's approach.
5 Ways to Handle Rust Errors and How Learning Traits Support Inheritance
Learning how to handle errors is very important when writing code. Errors are usually caused by users' misunderstanding of the program's purpose. Errors can not only serve as teaching tools for users, but also improve code efficiency. Unlike the logical conditions we use to control processes, errors are caused by some expectation not being met or an option not being available. Rust provides five different methods to directly address these "expected" and "unavailable" issues.
Errors Ultimately Serve Users and Developers
When users receive obscure and difficult to understand error feedback, they will feel frustrated. And developers hope to see a series of events or function calls that lead to crashes. This leads us to view errors as strings and structured types.
Observing How Python Handles Errors
Comparing and contrasting Python's error handling methods is very effective when learning Rust's error handling. This makes it easier to discover patterns.
Comparison Between Python and Rust
No Error Handling: The Main Function Throws an Exception and Fails
result = 10 / 0
print(result)
Output:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Try Except: Capture Boundary Conditions
try:
result = 10 / 0 # This will raise ZeroDivisionError
except ZeroDivisionError:
print("Cannot divide by zero!")
except TypeError:
print("Type error occurred!")
Try Except Finally: Ensure Proper Cleanup During Abnormal Periods
try:
file = open("example.txt", "r")
except FileNotFoundError:
print("File not found.")
finally:
print("Closing file (if open).")
if 'file' in locals() and not file.closed:
file.close()
Raising Exception: Crashing Code/Handled by Calling Function
age = -1
if age < 0:
raise ValueError("Age cannot be negative!")
Creating Custom Errors: Providing Structured Errors for Developers
class ApplicationError(Exception):
"""Base class for all application errors."""
pass
class DatabaseError(ApplicationError):
"""Exception raised for database-related errors."""
pass
class APIError(ApplicationError):
"""Exception raised for API-related errors."""
pass
# Usage
try:
raise DatabaseError("Unable to connect to the database.")
except DatabaseError as e:
print(f"Database error: {e}")
except ApplicationError:
print("General application error.")
Through these processes, the program can continue to run until the user closes the application. During this process, we can see how user requests are serviced and what supporting devices and applications are doing.
Observe How Rust Handles Errors
Now it's Rust's turn. The terms used in error handling, such as Panic, Unwrap, Expect, and '?', each prompt us to take action and write better code. Making code Panic is considered a bad practice in Rust. Well-defined error handling is crucial when building applications or libraries.
No Error Handling: Panic!
fn main() {
let x = 50;
let y = 0;
let rs = x / y;
println!("{}", rs)
}
Output:
thread 'main' panicked at 'attempt to divide by zero', src/main.rs:11:14
Catch Unwind: Capture Boundary Conditions
fn main() {
let x = 50;
let y = 10;
let cuw = std::panic::catch_unwind(|| x / y);
match cuw {
Ok(val) => println!("Got the {val}"),
Err(e) => println!("Error: {:?}", e),
}
}
Presence Detection in Rust: Match, Results, and Options
Rust is a strongly typed and memory safe language, which leads to the unique sub data types Options and Results of enum. Both deal with the 'existence' and 'non existence' of things that interest us. The match
keyword is used to check if the return type is any variant of enum. In the case of Option, it is None or a 'known Rust data type' that can be processed and used. In the case of Result, it is either Error or the 'value' we are pursuing.
Triggering Errors in Functions: Propagating Errors
use std::error::Error;
fn div_by_zero(a: i32, b: i32) -> Result<i32, Box<dyn Error>> {
if b == 0 {
return Err("Divide by 0, leads to infinity....".into());
}
Ok(a / b)
}
fn main() {
println!("Doing what catch unwind does, manually");
let err_rs = div_by_zero(57, 0);
let val_rs = div_by_zero(50, 5);
match err_rs {
Ok(val) => println!("Got {val}"),
Err(e) => eprintln!("Got {:?}", e),
}
match val_rs {
Ok(val) => println!("Got {val}"),
Err(e) => eprintln!("Got {:?}", e),
}
}
Create Custom Error: Provide Structured Error Output for Developers
use std::error::Error;
use std::fmt::Display;
#[derive(Debug)]
struct ZeroDivideError {
details: String,
}
impl ZeroDivideError {
fn new(msg: &str) -> ZeroDivideError {
ZeroDivideError {
details: msg.to_string(),
}
}
}
impl Error for ZeroDivideError {}
impl Display for ZeroDivideError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Zero divide: {}", self.details)
}
}
fn div_by_zero_error(a: i32, b: i32) -> Result<i32, ZeroDivideError> {
if b == 0 {
return Err(ZeroDivideError::new("Structured output"));
}
Ok(a / b)
}
fn main() {
println!("Creating Custom Errors");
let err_rs = div_by_zero_error(57, 0);
let val_rs = div_by_zero_error(50, 5);
match err_rs {
Ok(val) => println!("Got {val}"),
Err(e) => eprintln!("Got {:?}", e),
}
match val_rs {
Ok(val) => println!("Got {val}"),
Err(e) => eprintln!("Got {:?}", e),
}
}
Output:
Creating Custom Errors
Got ZeroDivideError { details: "Structured output" }
Got 10
The Error Type Depends on the Implemented Traits
ZeroDivideError is a standard structure in Rust. In Rust, a struct is equivalent to a class in Python. By adopting impl Error for ZeroDivideError
we have turned ZeroDivideError into an 'error type struct'. We still need to implement std::fmt::Display
for ZeroDivideError in order to display errors to users or developers.
Dynamically Handling Different Errors: Using Box
Box<dyn Error>
is very useful when functions may return different types of errors, and can handle these errors uniformly. Dynamic dispatch (dyn) achieves this by allowing runtime polymorphism.
use std::error::Error;
use std::fmt::Display;
#[derive(Debug)]
struct ZeroDivideError {
details: String,
}
impl ZeroDivideError {
fn new(msg: &str) -> ZeroDivideError {
ZeroDivideError {
details: msg.to_string(),
}
}
}
impl Error for ZeroDivideError {}
impl Display for ZeroDivideError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Zero divide: {}", self.details)
}
}
// Combining different error types using `Box<dyn Error>`
pub fn parse_and_double(input: &str) -> Result<i32, Box<dyn Error>> {
let number = input.parse::<i32>()?;
Ok(number * 2)
}
pub fn parse_and_dbl(input: &str) -> Result<i32, ZeroDivideError> {
let number = input.parse::<i32>();
match number {
Err(_) => {
return Err(ZeroDivideError::new(
"Negative number due to number parsing",
))
}
Ok(number) => return Ok(number * 2),
}
}
fn main() {
println!("Creating Custom Errors");
let parseme = parse_and_double("56,");
let prsme = parse_and_dbl("56,");
match parseme {
Ok(val) => println!("Got {val}"),
Err(e) => eprintln!("Got {:?}", e.to_string()),
}
match prsme {
Ok(val) => println!("Got {val}"),
Err(e) => eprintln!("Got {:?}", e.to_string()),
}
}
Output:
Creating Custom Errors
Got "invalid digit found in string"
Got "Zero divide: Negative number due to number parsing"
This is not all about error handling in Rust. In Rust, there are approximately 20 error handling patterns, but we have only seen 5 of them here. Practicing multiple error patterns will help to more easily identify these patterns.
Related Posts
GitHub Daily Recommendation 2025-01-23
🚀 GitHub Daily Recommendations Alert! 🌟 Dive into today's top open-source treasures, from cutting-edge AI tools to essential Web Frontend Development resources. 🌐✨ Don't miss out on the latest in Cloud Computing, Data Analysis, and more! 📊🔧 #OpenSourceProjects #Featured
Product Hunt Daily Recommendation 2025-01-23
Discover the latest Product Hunt daily picks for today's top innovations! Dive into hand-picked AI tools, SaaS products, and mobile apps. Explore, learn, and be the first to try out the newest tech trends. 🚀 #ProductHunt #DailyInnovations #TechTrends
GitHub Daily Recommendation 2025-01-21
🚀 GitHub Daily Recommendations Alert! 🌟 Dive into today's top-notch open-source projects, from cutting-edge AI tools to essential Web Frontend Development resources. 🌐 Enhance your coding skills and discover the best of open-source innovation right here! 📚✨