When doing rustlings standard_library_types/iterators2.rs
, I started wondering how std::iter::Iterator::map
calls its argument closure/function. More specifically, suppose I have a function
// "hello" -> "Hello"
pub fn capitalize_first(input: &str) -> String {
let mut c = input.chars();
match c.next() {
None => String::new(),
Some(first) => String::from(first.to_ascii_uppercase()) + c.as_str(),
}
}
Now I want to use it in
// Apply the `capitalize_first` function to a slice of string slices.
// Return a vector of strings.
// ["hello", "world"] -> ["Hello", "World"]
pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
words.into_iter().map(capitalize_first).collect()
}
which doesn't compile
error[E0631]: type mismatch in function arguments
--> exercises/standard_library_types/iterators2.rs:24:27
|
11 | pub fn capitalize_first(input: &str) -> String {
| ---------------------------------------------- found signature of `for<'r> fn(&'r str) -> _`
...
24 | words.into_iter().map(capitalize_first).collect()
| ^^^^^^^^^^^^^^^^ expected signature of `fn(&&str) -> _`
error[E0599]: the method `collect` exists for struct `Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> String {capitalize_first}>`, but its trait bounds were not satisfied
--> exercises/standard_library_types/iterators2.rs:24:45
|
24 | words.into_iter().map(capitalize_first).collect()
| ^^^^^^^ method cannot be called on `Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> String {capitalize_first}>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`<for<'r> fn(&'r str) -> String {capitalize_first} as FnOnce<(&&str,)>>::Output = _`
which is required by `Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> String {capitalize_first}>: Iterator`
`for<'r> fn(&'r str) -> String {capitalize_first}: FnMut<(&&str,)>`
which is required by `Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> String {capitalize_first}>: Iterator`
`Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> String {capitalize_first}>: Iterator`
which is required by `&mut Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> String {capitalize_first}>: Iterator`
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0599, E0631.
For more information about an error, try `rustc --explain E0599`.
However, it works fine after I changed .map(capitalize_first)
to .map(|x| capitalize_first(x))
. Apparently, Rust borrows (not sure mutable or immutable) each item before passing it to the argument closure/function of map
, which makes sense because we typically don't want to consume the objects being iterated over.
What I cannot understand is why Rust doesn't borrow the arguments to |x| capitalize_first(x)
. I hypothesize that the closure |x| capitalize_first(x)
still got &&str
, and then the auto-dereferencing rules kicked in and dereferenced it to &str
, but that doesn't explain why it didn't kick in when I'm using the function capitalize_first
. What's the difference between .map(|x| capitalize_first(x))
and .map(capitalize_first)
? Is dynamic dispatch happening here, given that the argument to map
is a trait object?
Note: this question is not a duplicate to Using a function with iter().map() - as a named function vs as a closure because I am asking why, whereas the other post asked how. Regarding the accepted answer to that question, I would appreciate it if someone could explain why we need AsRef
while Rust already has auto-dereferencing rules.
Why can I call capitalize_first
with a &&str
argument?
The linked Q&A for auto-dereferencing rules is specifically for how self
is resolved when using the a.b()
syntax. The rules for arguments in general skip the auto-reference step and just rely on deref coercions. Since &&str
implements Deref<Target = &str>
(and indeed all references implement Deref
), this &&str
-> &str
transformation happens transparently.
Why doesn't it work for .map(f)
then?
Plain and simply, map()
is expecting something that implements Fn(&&str) -> T
and capitalize_first
does not. A Fn(&str)
is not transparently transformed into a Fn(&&str)
, it requires a transformation step like the one introduced by the closure (albeit transparently).