Lesson 19 of 50 · rust
Error Handling with Result
Duration: 20 minutes
Lesson 19 of 50 · rust
Duration: 20 minutes
Result<T, E> represents operations that can succeed (Ok) or fail (Err). It's the standard for fallible operations in Rust.\n\n## The Result Type\n\nrust\nenum Result<T, E> {\n Ok(T),\n Err(E),\n}\n\n\n## Reading Files with Result\n\nrust\nuse std::fs::File;\nuse std::io::{self, Read};\n\nfn read_file(path: &str) -> Result<String, io::Error> {\n let mut file = File::open(path)?; // Question mark propagates errors\n let mut contents = String::new();\n file.read_to_string(&mut contents)?;\n Ok(contents)\n}\n\nfn main() {\n match read_file(\"data.txt\") {\n Ok(text) => println!(\"File contents: {}\", text),\n Err(e) => eprintln!(\"Error reading file: {}\", e),\n }\n}\n\n\n## The Question Mark Operator\n\nThe ? operator automatically propagates errors:\n\nrust\n// Without ?: verbose\nfn process() -> Result<i32, String> {\n let val = match risky_operation() {\n Ok(v) => v,\n Err(e) => return Err(e),\n };\n Ok(val * 2)\n}\n\n// With ?: concise\nfn process() -> Result<i32, String> {\n let val = risky_operation()?; // Return early if Err\n Ok(val * 2)\n}\n\nfn risky_operation() -> Result<i32, String> {\n // ...\n Ok(42)\n}\n\n\n## Result Methods\n\nrust\nfn main() {\n let x: Result<i32, String> = Ok(5);\n let y: Result<i32, String> = Err(String::from(\"error\"));\n \n // Check state\n println!(\"x.is_ok(): {}\", x.is_ok()); // true\n println!(\"y.is_err(): {}\", y.is_err()); // true\n \n // Extract with unwrap (panics if Err)\n let val = x.unwrap(); // 5\n // let bad = y.unwrap(); // panics!\n \n // Safe extraction with default\n let val2 = y.unwrap_or(0); // 0\n let val3 = y.unwrap_or_else(|e| -1); // -1\n \n // Transform values\n let doubled = x.map(|n| n * 2); // Ok(10)\n let error_msg = y.map_err(|e| format!(\"Error: {}\", e)); // Err(...)\n \n // Chain operations\n x.and_then(|n| Ok(n * 2))\n .and_then(|n| Ok(n + 1))\n .map(|n| println!(\"Result: {}\", n));\n}\n\n\n## Custom Error Types\n\nrust\n#[derive(Debug)]\nenum ParseError {\n InvalidFormat,\n Overflow,\n}\n\nfn parse_number(s: &str) -> Result<i32, ParseError> {\n if s.is_empty() {\n return Err(ParseError::InvalidFormat);\n }\n \n s.parse::<i32>().map_err(|_| ParseError::Overflow)\n}\n\nfn main() {\n match parse_number(\"42\") {\n Ok(n) => println!(\"Number: {}\", n),\n Err(e) => println!(\"Parse error: {:?}\", e),\n }\n}\n\n\n## Practical Example: Validation Pipeline\n\nrust\nfn validate_age(age: u32) -> Result<(), String> {\n if age < 0 {\n return Err(String::from(\"Age cannot be negative\"));\n }\n if age > 150 {\n return Err(String::from(\"Age seems unrealistic\"));\n }\n Ok(())\n}\n\nfn validate_email(email: &str) -> Result<(), String> {\n if !email.contains('@') {\n return Err(String::from(\"Invalid email\"));\n }\n Ok(())\n}\n\nfn register_user(email: &str, age: u32) -> Result<(), String> {\n validate_email(email)?;\n validate_age(age)?;\n println!(\"User registered!\");\n Ok(())\n}\n\nfn main() {\n match register_user(\"user@example.com\", 25) {\n Ok(_) => println!(\"Success\"),\n Err(e) => println!(\"Registration failed: {}\", e),\n }\n}\n\n\n## Error Handling Strategies\n\n1. Propagate with ?: Return errors to caller\n2. Handle with match: Explicitly handle each case\n3. Use unwrap_or: Provide sensible defaults\n4. Custom errors: Define domain-specific error types\n\n## Result vs Option\n\n- Option: When value may not exist\n- Result: When operation may fail with an error\n\nBoth enable safe, explicit error handling.