Custom Error Types
🎯 In Python, you define custom exceptions by subclassing:
class CalcError(Exception):
pass
class DivisionByZero(CalcError):
pass
class ParseError(CalcError):
def __init__(self, token):
self.token = token
Different error types, but they're all classes in an inheritance hierarchy. You catch the base class or specific ones.
Rust uses enums for error types instead of class hierarchies:
#[derive(Debug, PartialEq, Eq)]
enum CalcError {
DivisionByZero,
ParseInt(String),
BadFormat,
}
Each variant is a distinct error case. The enum carries data when needed (ParseInt(String) stores the bad token). No inheritance, no base class — the match expression handles each case exhaustively.
The ? operator
In exercise 09, you returned Result manually. Rust's ? operator chains operations that might fail:
fn process(input: &str) -> Result<Output, AppError> {
let parsed = parse(input)?; // if Err, return it immediately
let checked = validate(parsed)?; // if Err, return it immediately
transform(checked)
}
? is like Python's implicit exception propagation, but explicit — you see exactly where errors can occur and how they flow.
map_err for error conversion
When the error types don't match directly, map_err converts:
user_input
.parse::<f64>()
.map_err(|_| AppError::InvalidInput(user_input.to_string()))
This turns a std::num::ParseFloatError into your AppError::InvalidInput. Python's equivalent would be catching one exception and raising another.
Your Task
-
Define the
CalcErrorenum (already provided) with variants:DivisionByZero,ParseInt(String),BadFormat -
Implement three functions:
parse_int(tok: &str) -> Result<i32, CalcError>— trim and parse; returnParseInt(token)on failuresafe_div(a: i32, b: i32) -> Result<i32, CalcError>— returnDivisionByZeroifb == 0eval_div(expr: &str) -> Result<i32, CalcError>— parse"a / b"format, split on/, parse both sides, divide. ReturnBadFormatif not exactly two parts.
Example
use CalcError::*;
assert_eq!(eval_div("12/3"), Ok(4));
assert_eq!(eval_div(" 10 / 2 "), Ok(5));
assert_eq!(eval_div("7/0"), Err(DivisionByZero));
assert_eq!(eval_div("x/3"), Err(ParseInt("x".into())));
assert_eq!(eval_div("1/2/3"), Err(BadFormat));
Dive deeper: In our Rust Developer Cohort, you'll build a
JsonErrorenum with variants likeUnexpectedToken,UnterminatedString, andInvalidNumber— giving users precise diagnostics when parsing fails.
Further Reading
- The Rust Book — The ? Operator — error propagation
- The Rust Book — Defining an Enum — enums as error types
- Result::map_err — converting error types