Reputation:
I've got this function in Rust that capitalizes a string.
pub fn capitalize_first(input: &str) -> String {
let mut c = input.chars();
match c.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + c.as_str(),
}
}
Later, I use it to iterate over a vector of strings.
let words = vec!["hello", "world"];
let capitalized_words: Vec<String> =
words.iter().map(|word| capitalize_first(word)).collect();
This works as expected, but I notice that the closure |word| capitalize_first(word)
is pretty useless. So I tried to replace it with passing capitalize_first
directly like this.
let words = vec!["hello", "world"];
let capitalized_words: Vec<String> = words.iter().map(capitalize_first).collect();
This, however, fails to compile with the following error message.
10 | pub fn capitalize_first(input: &str) -> String {
| ---------------------------------------------- found signature of `for<'r> fn(&'r str) -> _`
...
38 | let capitalized_words: Vec<String> = words.iter().map(capitalize_first).collect();
| ^^^^^^^^^^^^^^^^ expected signature of `fn(&&str) -> _`
I'm having trouble understanding this error. Why does the closure work but passing the function directly does not. Is there something I can change that would allow me to pass the function reference instead of making a useless closure?
Upvotes: 5
Views: 2983
Reputation: 103713
When you iterate over a collection like you are with your call to words.iter()
, you are iterating over references to your elements. The elements in your vector are of type &str
, and so references to your elements are of type &&str
.
So map
is expecting a function which takes an argument of type &&str
, and so that is what the word
argument in your closure is inferred as. Then when you call capitalize_first
on word
, it is automatically dereferenced to &str
, due to the Deref
trait being implemented for all references.
However, even though your function will appear to accept arguments of type &&str
due to this conversion, the conversion happens outside your function. So it doesn't mean that your function can be passed in place of a function which expects &&str
.
There are 2 solutions. You can either change your function to be more generic, and have it accept anything that implements AsRef<str>
. Then it will accept anything that can be dereferenced to str
, which includes &str
, &&str
and String
, among others.
pub fn capitalize_first<S: AsRef<str>>(input: S) -> String {
let mut c = input.as_ref().chars();
// Don't forget this ^
match c.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + c.as_str(),
}
}
Or you can keep your function as it is, and fix it at the call site, by calling copied()
on your iterator, which will essentially dereference the elements.
let capitalized_words: Vec<String> = words.iter().copied().map(capitalize_first).collect();
I would recommend the first approach.
Upvotes: 7