โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
// Your application's unified error type
#[derive(Debug)]
enum AppError {
Parse(ParseError),
Math(MathError),
Io(String),
}
// Tell Rust: ParseError can become AppError
impl From<ParseError> for AppError {
fn from(e: ParseError) -> Self { AppError::Parse(e) }
}
// Tell Rust: MathError can become AppError
impl From<MathError> for AppError {
fn from(e: MathError) -> Self { AppError::Math(e) }
}
// These return different error types:
fn parse_positive(s: &str) -> Result<i64, ParseError> { ... }
fn reciprocal(x: i64) -> Result<f64, MathError> { ... }
// Now you can use ? on both โ conversions happen automatically!
fn pipeline(s: &str) -> Result<f64, AppError> {
let n = parse_positive(s)?; // ParseError โ AppError::Parse via From
let r = reciprocal(n)?; // MathError โ AppError::Math via From
Ok(r * 100.0)
}
// map_err: manual conversion when you don't have From
let result: Result<i64, AppError> =
"42".parse::<i64>()
.map_err(|e| AppError::Io(e.to_string())); // explicit conversion
// Into is the mirror of From โ once you implement From, Into is free
let pe = ParseError("bad input".to_string());
let ae: AppError = pe.into(); // calls From<ParseError> for AppError
| Concept | OCaml | Rust | ||
|---|---|---|---|---|
| Error unification | Manual wrapping or polymorphic variants | `enum AppError` + `From<E>` impls | ||
| Auto-conversion | Not built-in, use explicit wrapping | `?` calls `From::from()` automatically | ||
| Manual conversion | `Result.map_error` | `.map_err(\ | e\ | ...)` |
| Symmetric trait | N/A | `Into<T>` is auto-derived from `From<T>` | ||
| Std error types | `Stdlib` exceptions are all one type | Each module has own error type; `From` bridges them | ||
| Conversion cost | None (OCaml GC manages) | Zero-cost (Rust move semantics, no allocation) |
// Error Conversion โ 99 Problems #49
// Convert between error types using From/Into and map_err.
use std::num::ParseIntError;
use std::fmt;
// โโ Error types โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug)]
struct ParseError(String);
#[derive(Debug)]
struct MathError(String);
#[derive(Debug)]
enum AppError {
Parse(ParseError),
Math(MathError),
Io(String),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ParseError: {}", self.0)
}
}
impl fmt::Display for MathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MathError: {}", self.0)
}
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Parse(e) => write!(f, "{}", e),
AppError::Math(e) => write!(f, "{}", e),
AppError::Io(s) => write!(f, "IoError: {}", s),
}
}
}
// โโ From implementations enable ? operator โโโโโโโโโโโโโโโ
impl From<ParseError> for AppError {
fn from(e: ParseError) -> Self { AppError::Parse(e) }
}
impl From<MathError> for AppError {
fn from(e: MathError) -> Self { AppError::Math(e) }
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::Parse(ParseError(e.to_string()))
}
}
// โโ Functions returning specific error types โโโโโโโโโโโโโโ
fn parse_positive(s: &str) -> Result<i64, ParseError> {
let n: i64 = s.trim().parse().map_err(|_| ParseError(format!("not an int: {}", s)))?;
if n > 0 {
Ok(n)
} else {
Err(ParseError(format!("not positive: {}", n)))
}
}
fn reciprocal(x: i64) -> Result<f64, MathError> {
if x == 0 {
Err(MathError("reciprocal of zero".to_string()))
} else {
Ok(1.0 / x as f64)
}
}
/// Combines both error types; From lets ? convert automatically.
fn pipeline(s: &str) -> Result<f64, AppError> {
let n = parse_positive(s)?; // ParseError โ AppError via From
let r = reciprocal(n)?; // MathError โ AppError via From
Ok(r * 100.0)
}
fn main() {
let cases = ["5", "0", "-3", "abc"];
for s in &cases {
match pipeline(s) {
Ok(v) => println!("pipeline({:?}) = {:.4}", s, v),
Err(e) => println!("pipeline({:?}) = Error: {}", s, e),
}
}
// map_err: manual conversion
let converted: Result<i64, AppError> = "42".parse::<i64>()
.map_err(|e| AppError::Io(e.to_string()));
println!("\nmap_err demo: {:?}", converted);
// Into: the symmetric view of From
let pe = ParseError("bad input".to_string());
let ae: AppError = pe.into(); // calls From<ParseError>
println!("into AppError: {}", ae);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pipeline_ok() {
let r = pipeline("4").unwrap();
assert!((r - 25.0).abs() < 1e-9); // 100 / 4 = 25
}
#[test]
fn test_pipeline_parse_error() {
assert!(matches!(pipeline("abc"), Err(AppError::Parse(_))));
}
#[test]
fn test_pipeline_math_error() {
// parse_positive("0") fails with ParseError (not positive), not MathError
assert!(matches!(pipeline("0"), Err(AppError::Parse(_))));
}
#[test]
fn test_from_parse_int_error() {
let r: Result<i64, AppError> = "x".parse::<i64>().map_err(AppError::from);
assert!(matches!(r, Err(AppError::Parse(_))));
}
}
(* Error Conversion *)
(* OCaml 99 Problems #49 *)
(* Implementation for example 49 *)
(* Tests *)
let () =
(* Add tests *)
print_endline "โ OCaml tests passed"