vitiral
vitiral

Reputation: 9238

Some not required when returning Option?

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

Answers (1)

Veedrac
Veedrac

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

Related Questions