rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.98k stars 12.68k forks source link

Option map won't compile while match works well #47212

Open andreytkachenko opened 6 years ago

andreytkachenko commented 6 years ago

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.

bluss commented 6 years ago

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.

daboross commented 6 years ago

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!

Spoonbender commented 2 years ago

Triage: no change