If Rust does not have exceptions, then how do you handle errors in Rust?
Don't panic! Rust has Results

Search for a command to run...
Don't panic! Rust has Results

No comments yet. Be the first to comment.
This blog series explores the depth and versatility of the Rust programming language through powerful lenses such as Object-Oriented Programming (OOP), Functional Programming (FP), and Design Patterns.
Iterators are a fundamental concept in programming that enable you to process a sequence of items, such as elements in a collection, one at a time, without revealing the collection's internal structure. They form the backbone of operations like loopi...
Iterators are a fundamental concept in programming that enable you to process a sequence of items, such as elements in a collection, one at a time, without revealing the collection's internal structure. They form the backbone of operations like loopi...

A closure is an anonymous function that can capture variables from its environment. At a high level, Rust closures allow you to write small, concise functions inline, without having to formally define a new fn with a name. They’re particularly handy ...

Immutability is a cool concept in programming that C++ questions with "why?" It means that once you create a variable or object, you can't change its state. This idea somewhat opposes object-oriented concepts, yet it's crucial for writing clean, main...

Object-oriented programming, also called OOP, is a programming style that is dependent on the concept of objects. It is very popular and established. It is like designing and organizing your code by thinking of parts of your program as real-world obj...

Traits are the most important topic to understand the design patterns in Rust. Traits are fundamental feature in Rust that provide a way for shared behavior, abstraction and polymorphish. You can think of traits as interfaces in other programming la...

"Rust does not have exceptions".
This sounds very interesting, but what does it mean? Rust handles errors in a sophisticated manner, not like try-catch block type error handling. There are two approaches either panic for unrecoverable errors or use Result Enum for recoverable error. We could panic whenever we come across something messed up, a bug like divide by zero. Its not a crash, it means Rust walk back up the stack and clean up the data from each function it encounters. It’s more like a RuntimeException in Java or a std::logic_error in C++. Lets focus on Result as it the topic of discussion. The use of Result enum in error handling gives you a fresh perspective, potential best practices, and avoiding neglect. The use Result as a return type of functions forces you to think in a non-lazy approach about all the places/functions an error could occur and makes decisions like:
Propagating errors to caller functions.
Discarding errors or reacting to specific errors.
You end up thinking of error handling more in Rust as you need to decide if the function is fallible because the return type of function depends on it.
Let's first discuss the Result<T,E> enum here.
Result<T,E>Result is a built-in enum in the Rust standard library with two variants Ok(T) and Err(E).
Ok(T): returns the success statement of type T.
Err(E): returns the error statement of type E.
enum Result<T, E> {
Ok(T),
Err(E),
}
Result must be used as a return type for a function that can encounter error situations. Such functions can return an Ok value in case of success or an Err value in case of an error. Using Result as a function return type can be used to return various kinds of success and error codes to let the calling function decode the execution state of the called function.
fn abc(a:f64)->Result<f64,bool>{
if a ==20.0{
println!("Going good");
Ok(a/2.0)
}
else if a<20.0 {
Ok(a)
}
else {
println!("this is error");
Err(false)
}
}
match abc(10.0){
Ok(T)=>{
println!("What are you talking about? Here is no error!!")
}
Err(E)=>{
println!("Finally caught a error!")
}
}
match looks like a try/catch block. As we received the Result enum, it requires some processing of Result <T,E> if we want to handle it instead of passing it to the caller. There are various methods that Result <T,E> provides to handle it. For example
pub fn unwrap_or(self, default: T) -> T
This returns the contained Ok value or a provided default.
let r = abc(40.0).unwrap_or(0.0);
println!("The value of r is {}",r);
Rust has ? operator that helps to propagate the errors to the caller function. Any functions that return a Result, we can add ? like
let r = abc(20.0)?;
This unwraps the Result to get the value inside. In case of an error, it immediately returns from the enclosing functions, passing the error result up the call chain. This ? looks pretty cool, isn't it?
Let's say we are using ? in multiple lines that have different error types in Result.
fn abc(a:f64)->Result<f64,bool>{
let x = x(a)?;
let y = y(a)?;
Ok(0.0)
}
fn x(a:f64)->Result<f64,String>{
Err("Error in x".parse().unwrap())
}
fn y(a:f64)->Result<f64,bool>{
Err(false)
}
error[E0277]: `?` couldn't convert the error to `bool`
--> src\main.rs:42:21
|
41 | fn abc(a:f64)->Result<f64,bool>{
| ---------------- expected `bool` because of this
42 | let x = x(a)?;
| ^ the trait `From<String>` is not implemented for `bool`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following other types implement trait `FromResidual<R>`:
<Result<T, F> as FromResidual<Result<Infallible, E>>>
<Result<T, F> as FromResidual<Yeet<E>>>
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, String>>` for `Result<f64, bool>`
The problem is two different types of errors types String and bool. We can deal with this using the Generic type.
type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;
type GenericResult<T> = Result<T, GenericError>;
Result<T, !>Often you need a function return type as Result to match trait signatures even though the function never fails. Rust provides the never type, written with the ! syntax. The never type represents a value that can never be generated.