Lesson 18 of 50 · rust
Error Handling with Option
Duration: 15 minutes
Lesson 18 of 50 · rust
Duration: 15 minutes
Option<T> represents a value that may or may not exist. It replaces null pointers from other languages, making the absence of a value explicit in the type system.\n\n## The Option Type\n\nrust\nenum Option<T> {\n Some(T),\n None,\n}\n\n\nIt's so common you don't need to prefix it with std::option::\n\n## Creating Options\n\nrust\nfn main() {\n let some_number = Some(5);\n let none_number: Option<i32> = None;\n \n let x: Option<String> = Some(String::from(\"hello\"));\n let y: Option<String> = None;\n}\n\n\n## Matching Options\n\nThe most explicit way to handle Option values:\n\nrust\nfn find_even(nums: &[i32]) -> Option<i32> {\n for &n in nums {\n if n % 2 == 0 {\n return Some(n);\n }\n }\n None\n}\n\nfn main() {\n let numbers = [1, 3, 5, 8, 9];\n match find_even(&numbers) {\n Some(e) => println!(\"First even: {}\", e),\n None => println!(\"No even numbers\"),\n }\n}\n\n\n## If Let Shorthand\n\nFor cases where you only care about one variant:\n\nrust\nfn main() {\n let x: Option<i32> = Some(5);\n \n // Verbose match\n match x {\n Some(n) => println!(\"Value: {}\", n),\n None => (),\n }\n \n // Concise if let\n if let Some(n) = x {\n println!(\"Value: {}\", n);\n }\n}\n\n\n## Option Methods\n\nrust\nfn main() {\n let x: Option<i32> = Some(5);\n let y: Option<i32> = None;\n \n // Check existence\n println!(\"x.is_some(): {}\", x.is_some()); // true\n println!(\"y.is_none(): {}\", y.is_none()); // true\n \n // Extract with unwrap (panics if None)\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(|| 42); // 42\n \n // Transform values\n let doubled = x.map(|n| n * 2); // Some(10)\n let none_doubled = y.map(|n| n * 2); // None\n \n // Chain operations\n x.map(|n| n * 2)\n .map(|n| n + 1)\n .map(|n| println!(\"Result: {}\", n)); // None\n}\n\n\n## Practical Example: Parsing Configuration\n\nrust\nfn get_config_value(config: &std::collections::HashMap<String, String>, key: &str) -> Option<i32> {\n config.get(key)\n .and_then(|val| val.parse::<i32>().ok())\n}\n\nfn main() {\n let mut config = std::collections::HashMap::new();\n config.insert(String::from(\"timeout\"), String::from(\"30\"));\n \n match get_config_value(&config, \"timeout\") {\n Some(timeout) => println!(\"Timeout: {} seconds\", timeout),\n None => println!(\"Invalid or missing timeout\"),\n }\n}\n\n\n## The Question Mark Operator\n\nFor propagating None values early:\n\nrust\nfn main() {\n let x: Option<i32> = Some(5);\n \n // Traditional approach\n let y = match x {\n Some(val) => Some(val * 2),\n None => return None,\n };\n \n // But Option<T> expects the function to also return Option\n}\n\nfn double_optional(x: Option<i32>) -> Option<i32> {\n Some(x? * 2) // Returns None if x is None\n}\n\n\n## Why Option Over Null?\n\n1. Explicit: Absence is part of the type\n2. Safe: Can't access None as a value\n3. Composable: Chain operations with map, and_then\n4. Forces handling: Compiler requires you to handle None case