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

⚓ Rust    📅 2026-04-12    👤 surdeus    👁️ 7      

surdeus

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 (click for more details) Compiler error for the Cow case (click for more details)

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.

2 posts - 2 participants

Read full topic

🏷️ Rust_feed