Reputation: 9238
I am doing the more combinators part of "Rust by Example", and decided to go off on my own and see how much effort it is to use map
instead of and_then
.
In my attempt, I came across something very strange (it even looks like a compiler error to me). It looks like I only need to return a Food
type when the return type is supposed to be Option(Food)
In my opinion, the cookable
function below should be able to be reduced to a single line:
have_ingredients(food).map(|f| can_cook(f))
Obviously it can also be the following:
have_ingredients(food).and_then(can_cook)
Although I don't see the fundamental difference between these two functions, as they both return an Option<U>
.
I got a weird compiler error when doing so, so I explicitly broke down the match as below -- and looks like the compiler wants to return Food
even when the return type is Some(Food)
. What is going on???
//! stack.rs
#[derive(Debug)]
enum Food {
CordonBleu,
Steak,
Sushi,
}
#[derive(Debug)]
enum Day {
Monday,
Tuesday,
Wednesday,
}
/// we don't have the ingredients for sushi
fn have_ingredients(food: Food) -> Option<Food> {
match food {
Food::Sushi => None,
_ => Some(food),
}
}
/// can cook anything but cordon blue
fn can_cook(food: Food) -> Option<Food> {
match food {
Food::CordonBlue => None,
_ => Some(food),
}
}
/// can be done using map
fn cookable(food: Food) -> Option<Food> {
match have_ingredients(food).map(|f| can_cook(f)) {
// Some(food) => food, // Why is this correct???
Some(food) => Some(food), // **error: mismatched types:
None => None,
}
}
fn eat(food: Food, day: Day) {
match cookable(food) {
Some(food) => println!("Yay! On {:?} we eat {:?}", day, food),
None => println!("Oh no we didn'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);
}
Here is the full compiler error from the above program:
ch16_errors git:(master) ✗ rustc stack.rs
stack.rs:38:28: 38:32 error: mismatched types:
expected `Food`,
found `core::option::Option<Food>`
(expected enum `Food`,
found enum `core::option::Option`) [E0308]
stack.rs:38 Some(food) => Some(food),
^~~~
stack.rs:38:28: 38:32 help: run `rustc --explain E0308` to see a detailed explanation
error: aborting due to previous error
Upvotes: 3
Views: 89
Reputation: 60187
have_ingredients(food).map(|f| can_cook(f))
gives an Option<Option<Food>>
, not an Option<Food>
, since map
does not flatten values.
Consider
Option<T>::map(Fn(T) -> U)
This transforms Option<T>
to Option<U>
. Thus letting T = Food
, U = Option<Food>
as in can_cook
gives the instantiation
Option<Food>::map(Fn(Food) -> Option<Food>)
which gives an Option<Option<Food>>
.
Thus the Some(food)
in the match has food
of type Option<Food>
.
and_then
flattens the result type, which is why this does not occur with it.
Upvotes: 6