Option map won't compile while match works well

7eb089b
Opened by Andrey Tkachenko at 2022-03-22 07:28:56

Consider this snippet:

fn xxx() -> Option<&'static str> {
    Some("word1 word2 word3")
}

fn to_words<'a>() -> Option<Box<Iterator<Item = &'a str> + 'a>> {
    xxx().map(|x|Box::new(x.split(' ')))
}

fn main() {
    println!("{}", to_words().unwrap().count());
}

it won't compiles with the error:

error[E0308]: mismatched types
 --> src/main.rs:6:5
  |
5 | fn to_words<'a>() -> Option<Box<Iterator<Item = &'a str> + 'a>> {
  |                      ------------------------------------------ expected `std::option::Option<std::boxed::Box<std::iter::Iterator<Item=&'a str> + 'a>>` because of return type
6 |     xxx().map(|x|Box::new(x.split(' ')))
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait std::iter::Iterator, found struct `std::str::Split`
  |
  = note: expected type `std::option::Option<std::boxed::Box<std::iter::Iterator<Item=&'a str> + 'a>>`
             found type `std::option::Option<std::boxed::Box<std::str::Split<'_, char>>>`

but match in exactly same case compiles well:

fn xxx() -> Option<&'static str> {
    Some("word1 word2 word3")
}

fn to_words<'a>() -> Option<Box<Iterator<Item = &'a str> + 'a>> {
    match xxx() {
        Some(x) => Some(Box::new(x.split(' '))),
        None => None
    }
}

fn main() {
    println!("{}", to_words().unwrap().count());
}

And if I explicitly define closure's argument type and returning value type it also compiles well:

fn xxx() -> Option<&'static str> {
    Some("word1 word2 word3")
}

fn to_words<'a>() -> Option<Box<Iterator<Item = &'a str> + 'a>> {
    let f: fn(x: &'a str) -> Box<Iterator<Item = &'a str>> = |x| {
        Box::new(x.split(' '))
    };
    
    xxx().map(f)
}

fn main() {
    println!("{}", to_words().unwrap().count());
}

I am sure there should not be differences between match and map at all.

This issue is reproduceble on rust stable and nightly.

  1. There is a difference -- you need a coercion to convert from the concrete type of some boxed split iterator Box<Split<..> into Box<Iterator<..>> (unsizing coercion). The .map() version does not support this, or rather, a value inside the map's closure will not coerce due to the expected type outside the map. I haven't found an open or closed issue on this topic, but there might be one already.

    bluss at 2018-01-05 22:42:07

  2. For completeness: the simplest explicit way to get this to compile is

    fn to_words<'a>() -> Option<Box<Iterator<Item = &'a str> + 'a>> {
        xxx().map(|x| Box::new(x.split(' ')) as Box<Iterator<Item = _>>)
    }
    

    It would be awesome if the compiler could infer casts like this through closure boundaries though!

    Dabo at 2018-01-07 05:07:50

  3. Triage: no change

    Maayan Hanin at 2022-03-22 07:28:56