Error Propagation
Forwarding errors up the call chain with the ? operator.
Error Propagation
Error propagation means that when a function receives an error, it forwards it to the calling function instead of handling it locally.
? Operator
The ? operator in Rust allows automatic error propagation:
RUSTfn read_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
The ? operator:
- If
Ok(value), unwraps the value - If
Err(error), immediately returns the Err
Manual Propagation
Before the ? operator, we had to do it manually:
RUSTfn read_file() -> Result<String, io::Error> { let mut f = match File::open("hello.txt") { Ok(file) => file, Err(e) => return Err(e), // Manual propagation }; // ... }
Handling Multiple Error Types
RUSTuse std::num::ParseIntError; fn parse_and_double(s: &str) -> Result<i32, ParseIntError> { let num = s.parse::<i32>()?; Ok(num * 2) }
Custom Error Types
RUST#[derive(Debug)] enum MyError { ParseError, FileError, }
Code Examples
Using ? Operator
Propagating errors with the ? operator
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
return Err(String::from("Division by zero"));
}
Ok(a / b)
}
fn calculate(a: f64, b: f64, c: f64) -> Result<f64, String> {
let step1 = divide(a, b)?;
let step2 = divide(step1, c)?;
Ok(step2)
}
fn main() {
match calculate(100.0, 2.0, 5.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}Explanation:
The ? operator automatically propagates the error. If divide returns Err, the calculate function immediately returns the Err.
Multiple Error Types
Handling different error types
use std::num::ParseIntError;
fn parse_number(s: &str) -> Result<i32, ParseIntError> {
s.parse::<i32>()
}
fn double_string(s: &str) -> Result<i32, ParseIntError> {
let num = parse_number(s)?;
Ok(num * 2)
}
fn main() {
match double_string("42") {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {:?}", e),
}
}Explanation:
The ? operator only works if the error type is compatible. ParseIntError is automatically propagated.
Manual Propagation
Handling errors with match (before ? operator)
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
return Err(String::from("Division by zero"));
}
Ok(a / b)
}
fn calculate_manual(a: f64, b: f64, c: f64) -> Result<f64, String> {
let step1 = match divide(a, b) {
Ok(value) => value,
Err(e) => return Err(e),
};
let step2 = match divide(step1, c) {
Ok(value) => value,
Err(e) => return Err(e),
};
Ok(step2)
}
fn main() {
match calculate_manual(100.0, 2.0, 5.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}Explanation:
Manual propagation with match is longer but does the same as the ? operator. The ? operator compresses this.
Exercises
Error Propagation
Use the ? operator in a function chain!
Starter Code:
fn step1(x: i32) -> Result<i32, String> {
if x < 0 {
Err(String::from("Negative number"))
} else {
Ok(x * 2)
}
}
fn step2(x: i32) -> Result<i32, String> {
if x > 100 {
Err(String::from("Number too large"))
} else {
Ok(x + 10)
}
}
fn main() {
match process(5) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}