7

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?

1 Answer 1

8

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.

Sign up to request clarification or add additional context in comments.

1 Comment

I can across this problem when playing with the iterators2.rs exercise in Rustlings. It was quite surprising! Why do you recommend the AsRef solution?