Iterating over Option
Option<T> trong Rust implement IntoIterator, điều này cho phép chúng ta sử dụng Option trong các iterator chains một cách elegant và expressive.
Cơ bản
Option<T> hoạt động như một iterator có 0 hoặc 1 phần tử:
fn main() { let some = Some(5); let none: Option<i32> = None; // Some(5) iterate 1 lần for value in some { println!("Value: {}", value); // Prints: Value: 5 } // None không iterate lần nào for value in none { println!("This won't print"); } }
Tại sao hữu dụng?
1. Combining với iterator methods
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let filter_value = Some(3); let result: Vec<i32> = numbers .into_iter() .filter(|&n| n > 2) .chain(filter_value) // Thêm Option vào iterator chain .collect(); println!("{:?}", result); // [3, 4, 5, 3] }
2. FlatMap với Option
fn parse_number(s: &str) -> Option<i32> { s.parse().ok() } fn main() { let strings = vec!["1", "two", "3", "four", "5"]; let numbers: Vec<i32> = strings .iter() .flat_map(|s| parse_number(s)) // flat_map tự động unwrap Option .collect(); println!("{:?}", numbers); // [1, 3, 5] }
3. Filter_map alternative
fn main() { let values = vec![Some(1), None, Some(3), None, Some(5)]; // Cách 1: filter + map let sum1: i32 = values.iter() .filter(|v| v.is_some()) .map(|v| v.unwrap()) .sum(); // Cách 2: filter_map (idiomatic) let sum2: i32 = values.iter() .filter_map(|&v| v) .sum(); // Cách 3: flatten (Option implements IntoIterator) let sum3: i32 = values.iter() .flatten() .sum(); println!("{}, {}, {}", sum1, sum2, sum3); // 9, 9, 9 }
Use Cases thực tế
1. Optional configuration
#![allow(unused)] fn main() { #[derive(Debug)] struct Config { host: String, port: u16, } impl Config { fn from_env() -> Self { let custom_host = std::env::var("HOST").ok(); let custom_port = std::env::var("PORT").ok() .and_then(|s| s.parse().ok()); let default_host = "localhost".to_string(); let default_port = 8080; // Sử dụng chain để ưu tiên custom values Config { host: custom_host.into_iter() .chain(Some(default_host)) .next() .unwrap(), port: custom_port.into_iter() .chain(Some(default_port)) .next() .unwrap(), } } } }
2. Collecting optional values
fn get_user_by_id(id: u32) -> Option<String> { match id { 1 => Some("Alice".to_string()), 2 => Some("Bob".to_string()), _ => None, } } fn main() { let user_ids = vec![1, 3, 2, 5]; // Collect tất cả users tồn tại let users: Vec<String> = user_ids .into_iter() .filter_map(get_user_by_id) .collect(); println!("{:?}", users); // ["Alice", "Bob"] }
3. Chaining operations
fn divide(a: f64, b: f64) -> Option<f64> { if b == 0.0 { None } else { Some(a / b) } } fn main() { let result = divide(10.0, 2.0) .into_iter() .map(|x| x * 2.0) .map(|x| x + 1.0) .next(); println!("{:?}", result); // Some(11.0) let error = divide(10.0, 0.0) .into_iter() .map(|x| x * 2.0) .next(); println!("{:?}", error); // None }
4. Flattening nested Options
fn main() { let nested = vec![ Some(Some(1)), Some(None), None, Some(Some(4)), ]; // Flatten 2 levels let flat: Vec<i32> = nested .into_iter() .flatten() // Option<Option<i32>> -> Option<i32> .flatten() // Option<i32> -> i32 .collect(); println!("{:?}", flat); // [1, 4] }
So sánh các approaches
Kiểm tra và unwrap
#![allow(unused)] fn main() { // ❌ Verbose và unsafe let value = if option.is_some() { option.unwrap() } else { default_value }; // ✅ Idiomatic let value = option.unwrap_or(default_value); }
Iterate và collect
#![allow(unused)] fn main() { let options = vec![Some(1), None, Some(3)]; // ❌ Manual loop let mut result = Vec::new(); for opt in options { if let Some(val) = opt { result.push(val); } } // ✅ Using flatten let result: Vec<i32> = options.into_iter().flatten().collect(); }
Advanced: Custom iteration
struct MaybeValue<T> { value: Option<T>, } impl<T> MaybeValue<T> { fn new(value: Option<T>) -> Self { MaybeValue { value } } fn process<F, U>(self, f: F) -> Option<U> where F: FnOnce(T) -> U, { self.value.into_iter().map(f).next() } } fn main() { let maybe = MaybeValue::new(Some(5)); let result = maybe.process(|x| x * 2); println!("{:?}", result); // Some(10) let empty = MaybeValue::new(None); let result = empty.process(|x: i32| x * 2); println!("{:?}", result); // None }
Option methods tương tự iterator
Option cung cấp nhiều methods tương tự iterator:
fn main() { let value = Some(5); // map let doubled = value.map(|x| x * 2); println!("{:?}", doubled); // Some(10) // filter let filtered = value.filter(|&x| x > 3); println!("{:?}", filtered); // Some(5) // and_then (flatMap) let result = value.and_then(|x| { if x > 3 { Some(x * 2) } else { None } }); println!("{:?}", result); // Some(10) }
Kết hợp với Result
fn parse_and_double(s: &str) -> Option<i32> { s.parse::<i32>() .ok() // Result -> Option .map(|n| n * 2) } fn main() { let strings = vec!["1", "two", "3"]; let numbers: Vec<i32> = strings .iter() .filter_map(|s| parse_and_double(s)) .collect(); println!("{:?}", numbers); // [2, 6] }
Performance notes
Iterate over Option không có overhead so với if-let hay match:
#![allow(unused)] fn main() { // Tất cả đều compile thành cùng assembly code: // 1. Using iterator for value in option { process(value); } // 2. Using if-let if let Some(value) = option { process(value); } // 3. Using match match option { Some(value) => process(value), None => {} } }
Best Practices
- Use
flatten()thay vìfilter_map(|x| x)cho clarity - Use
filter_map()khi cần transform + filter - Combine với iterator chains cho expressive code
- Avoid unnecessary unwrap - let iterator handle
None
Common patterns
fn main() { let options = vec![Some(1), None, Some(3), None, Some(5)]; // Sum của tất cả Some values let sum: i32 = options.iter().flatten().sum(); println!("Sum: {}", sum); // 9 // Count số lượng Some values let count = options.iter().flatten().count(); println!("Count: {}", count); // 3 // Find first Some value let first = options.iter().flatten().next(); println!("First: {:?}", first); // Some(1) // Check có ít nhất 1 Some value let has_some = options.iter().flatten().next().is_some(); println!("Has some: {}", has_some); // true }