On having to use closures (or generics) to pass coercible arguments

Suppose we have (any of) the following definitions:

let strs = [
    "a",
    "b",
];

let strings = [
    String::from("a"),
    String::from("b"),
];

let cow_strings = [
    Cow::from("a"),
    Cow::from(String::from("b")),
];

Rust makes our lives easier with deref coercion, allowing us to refer elements from any of these arrays as an argument to a function that takes a &str. E.g.

fn takes_ref_str(s: &str) -> usize {
    s.len()
}

println!("Len of a &str: {}", takes_ref_str(&strs[0])); // (&&str works too)
println!("Len of a String: {}", takes_ref_str(&strings[0]));
println!("Len of a copy-on-write string: {}", takes_ref_str(&cow_strings[0]));

What I find frustrating is that the function can't be used directly with iterators:

let _str_maps = strs
    .iter()
    .map(takes_ref_str);

gives

error[E0631]: type mismatch in function arguments
   --> src/main.rs:44:14
    |
  5 | fn takes_ref_str(s: &str) -> usize {
    | ---------------------------------- found signature defined here
...
 44 |         .map(takes_ref_str);
    |          --- ^^^^^^^^^^^^^ expected due to this
    |          |
    |          required by a bound introduced by this call
    |
    = note: expected function signature `fn(&&_) -> _`
               found function signature `fn(&_) -> _`
note: required by a bound in `map`
   --> .rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:780:12
    |
777 |     fn map<B, F>(self, f: F) -> Map<Self, F>
    |        --- required by a bound in this associated function
...
780 |         F: FnMut(Self::Item) -> B,
    |            ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Iterator::map`
help: consider wrapping the function in a closure
    |
 44 |         .map(|s: &&str| takes_ref_str(*s));
    |              ++++++++++              ++++
help: consider adjusting the signature so it borrows its argument
    |
  5 | fn takes_ref_str(s: &&str) -> usize {
    |                     +

For more information about this error, try `rustc --explain E0631`.

This specific case is easy to fix by just using .into_iter() instead. However, the String and Cow cases below are a bit different.

let _string_maps = strings
    .iter()
    .map(takes_ref_str);

let _cow_string_maps = cow_strings
    .iter()
    .map(takes_ref_str);
Compiler error for the String case
error[E0631]: type mismatch in function arguments
   --> src/main.rs:50:14
    |
  5 | fn takes_ref_str(s: &str) -> usize {
    | ---------------------------------- found signature defined here
...
 50 |         .map(takes_ref_str);
    |          --- ^^^^^^^^^^^^^ expected due to this
    |          |
    |          required by a bound introduced by this call
    |
    = note: expected function signature `fn(&String) -> _`
               found function signature `fn(&str) -> _`
note: required by a bound in `map`
   --> .rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:780:12
    |
777 |     fn map<B, F>(self, f: F) -> Map<Self, F>
    |        --- required by a bound in this associated function
...
780 |         F: FnMut(Self::Item) -> B,
    |            ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Iterator::map`
help: consider wrapping the function in a closure
    |
 50 |         .map(|arg0: &String| takes_ref_str(/* &str */));
    |              +++++++++++++++              ++++++++++++

For more information about this error, try `rustc --explain E0631`.
Compiler error for the Cow case
error[E0631]: type mismatch in function arguments
   --> src/main.rs:55:14
    |
  5 | fn takes_ref_str(s: &str) -> usize {
    | ---------------------------------- found signature defined here
...
 55 |         .map(takes_ref_str);
    |          --- ^^^^^^^^^^^^^ expected due to this
    |          |
    |          required by a bound introduced by this call
    |
    = note: expected function signature `fn(&Cow<'_, str>) -> _`
               found function signature `fn(&str) -> _`
note: required by a bound in `map`
   --> .rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:780:12
    |
777 |     fn map<B, F>(self, f: F) -> Map<Self, F>
    |        --- required by a bound in this associated function
...
780 |         F: FnMut(Self::Item) -> B,
    |            ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Iterator::map`
help: consider wrapping the function in a closure
    |
 55 |         .map(|arg0: &Cow<'_, str>| takes_ref_str(/* &str */));
    |              +++++++++++++++++++++              ++++++++++++

For more information about this error, try `rustc --explain E0631`.

We could try to work around this by adjusting the function definition. For example, both of the following functions are happily accepted, as is, by .map()s of .iter()s of any of the three arrays:

fn takes_ref_of_coercible_to_str<T: Deref<Target = str>>(s: &T) -> usize {
    s.len()
}

fn takes_referenceable_as_str<T: AsRef<str>>(s: T) -> usize {
    s.as_ref().len()
}

However, their complexity makes them deeply unsatisfactory.

Seems like pretty much the only choice left at the time of this writing is to do as the compiler says and wrap the function calls in closures:

let _str_maps = strs
    .iter()
    .map(|s| takes_ref_str(s));

let _string_maps = strings
    .iter()
    .map(|s| takes_ref_str(s));

let _cow_string_maps = cow_strings
    .iter()
    .map(|s| takes_ref_str(s));

While wrapping my head around this, I found this StackOverflow question, whose accepted answer explains a couple of iterations of whys on the problem: While the deref coercion from, for example, &String to &str exists, a deref coercion from fn(&String) -> T to fn(&str) -> T doesn't, and the former is what happens when using the closure, and the latter is what the developer is trying to (unsuccessfully) use when passing the function directly to map.

Could there be something different about the compiler errors to help understand the problem better? What currently feels off about them, to me, is that it seems likely that one would run into the problem by already knowing about 1. deref coercions, and 2. that functions such as map don't need to be called with a closure when a function name can just be used.

I don't know how many people are consciously thinking "impl Fn(&str) should coerce to impl Fn(&String)" so much as instinctively filling in the function name because they can call it directly. Anyway, perhaps the long form could be expanded with something like a &String example that explains in a bit more detail. (The inline error is already quite long IMO, 20+ lines before the correct suggestion.)

Yeah, the intuition, or following that instinct here, is what got me wondering about the reason it doesn't work. :slight_smile:

That sounds like a good idea to me!

After making this post, URLO now shows some related threads[1][2][3] discussing the same topic. I think it would be great to get the actual insight on "why doesn't this work, even though, based on prior experience, it looks like it maybe should?" from the error message, but I agree the compiler's output is already rather verbose.