Rust by Example中文

16.3 More combinators

map() was previously described as a way to simplify a match which also allows chaining. However, map() does not work for all cases because the constituents often occur in many different combinations. Consider the following example:

#![allow(dead_code)] #[derive(Debug)] enum Food { CordonBleu, Steak, Sushi } #[derive(Debug)] enum Day { Monday, Tuesday, Wednesday } // We don't have the ingredients to make Sushi. fn have_ingredients(food: Food) -> Option<Food> { match food { Food::Sushi => None, _ => Some(food), } } // We know how to make everything except Cordon Bleu. fn can_cook(food: Food) -> Option<Food> { match food { Food::CordonBleu => None, _ => Some(food), } } // To make a meal, we require both the ingredients and the ability to make that // meal, which is only possible when both are true; thus successes chain. // Conveniently, this can be rewritten more compactly with `and_then()`. fn cookable_v1(food: Food) -> Option<Food> { match have_ingredients(food) { None => None, Some(food) => match can_cook(food) { None => None, Some(food) => Some(food), }, } } // Same as `v1` above but uses `and_then()` instead. fn cookable_v2(food: Food) -> Option<Food> { have_ingredients(food).and_then(can_cook) } fn eat(food: Food, day: Day) { match cookable_v2(food) { Some(food) => println!("Yay! On {:?} we get to eat {:?}.", day, food), None => println!("Oh no. We don't get to eat on {:?}?", day), } } fn main() { let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi); eat(cordon_bleu, Day::Monday); eat(steak, Day::Tuesday); eat(sushi, Day::Wednesday); }

The reason this worked is because and_then() happened to require the exact function type as an input that was needed here. map() did not. Comparing the signatures of their input types, you will see that when the function returned an Option, and_then() became the one and only valid choice.

map():      FnOnce(T) -> U
and_then(): FnOnce(T) -> Option<U>

These are just two of many different combinators that are implemented on Option by the std library for many different use cases. It is advantageous to become familiar with them because they can simplify many error handling procedures and avoid the ugly and suicidal panic!() alternative. The other common error handling type, Result, also uses most of these same constructs so the skills are transferable.

See also:

Option, Option::map(), and Option::and_then()