Rust Interview Questions
Rust
Web DevelopmentIoTQuestion 14
How do you handle errors in Rust?
Answer:
Handling errors in Rust involves using the Result
and Option
types, along with various methods and the ?
operator to manage and propagate errors effectively. Here's a comprehensive guide on how to handle errors in Rust:
Using Result
and Option
1. Result
Type
The Result
type is used for functions that can return an error. It is defined as:
enum Result<T, E> {
Ok(T),
Err(E),
}
Ok(T)
: Indicates a successful result containing a value of typeT
.Err(E)
: Indicates an error containing a value of typeE
.
2. Option
Type
The Option
type is used for values that may or may not be present. It is defined as:
enum Option<T> {
Some(T),
None,
}
Some(T)
: Contains a value of typeT
.None
: Represents the absence of a value.
Handling Result
and Option
1. Using match
Statements
You can handle Result
and Option
values using match
statements to explicitly handle success and failure cases.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(numerator / denominator)
}
}
fn main() {
match divide(4.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
2. Using Methods like unwrap
, unwrap_or
, and unwrap_or_else
You can use these methods to handle Result
and Option
values more concisely.
fn main() {
let result = divide(10.0, 2.0).unwrap_or(0.0);
println!("Result or default: {}", result);
let result = divide(10.0, 0.0).unwrap_or_else(|err| {
println!("Handling error: {}", err);
0.0
});
println!("Result or handled error: {}", result);
}
3. Using the ?
Operator
The ?
operator can be used to propagate errors. It can only be used in functions that return Result
or Option
.
fn read_number_from_file(file_path: &str) -> Result<f64, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(file_path)?;
let number: f64 = content.trim().parse()?;
Ok(number)
}
fn main() {
match read_number_from_file("number.txt") {
Ok(number) => println!("Number from file: {}", number),
Err(e) => println!("Error: {}", e),
}
}
Custom Error Types
1. Defining Custom Error Types
You can define your own error types to provide more specific error handling.
#[derive(Debug)]
enum CustomError {
IoError(std::io::Error),
ParseError(std::num::ParseFloatError),
}
impl From<std::io::Error> for CustomError {
fn from(error: std::io::Error) -> Self {
CustomError::IoError(error)
}
}
impl From<std::num::ParseFloatError> for CustomError {
fn from(error: std::num::ParseFloatError) -> Self {
CustomError::ParseError(error)
}
}
fn read_number_from_file(file_path: &str) -> Result<f64, CustomError> {
let content = std::fs::read_to_string(file_path)?;
let number: f64 = content.trim().parse()?;
Ok(number)
}
fn main() {
match read_number_from_file("number.txt") {
Ok(number) => println!("Number from file: {}", number),
Err(e) => println!("Error: {:?}", e),
}
}
Using Result
with Multiple Error Types
Rust's Result
type can handle multiple error types using the From
trait to convert errors into a common error type.
fn read_file(file_path: &str) -> Result<String, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(file_path)?;
Ok(content)
}
fn parse_number(content: &str) -> Result<f64, Box<dyn std::error::Error>> {
let number: f64 = content.trim().parse()?;
Ok(number)
}
fn main() {
let result = read_file("number.txt").and_then(|content| parse_number(&content));
match result {
Ok(number) => println!("Parsed number: {}", number),
Err(e) => println!("Error: {}", e),
}
}
Summary
Rust provides robust tools for error handling with the Result
and Option
types. Using these types, along with methods like unwrap
, unwrap_or
, and unwrap_or_else
, and the ?
operator, you can handle errors effectively and write safer, more reliable code. Custom error types and the ability to convert between error types using the From
trait provide further flexibility in managing errors in complex applications.