Skip to main content
Brave Programmer Logo

BraveProgrammer

BraveProgrammer

HomeProjectsBlogsCoursesLessonsAbout

Site footer

BraveProgrammer

Free coding courses, practical tutorials, and real projects from BraveProgrammer. Learn web development with React, Next.js, and TypeScript.

Navigation

  • Home
  • Projects
  • Blogs
  • Courses

Resources

  • About
  • Lessons

© 2026 BraveProgrammer. All rights reserved.

  1. Courses
  2. /
  3. Rust Fundamentals

Lesson 19 of 50 · rust

Error Handling with Result

Duration: 20 minutes

Error Handling with Result\n\nResult<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.

Previous: Error Handling with OptionNext: Panic and Unwinding