6

Coming from Python, these two snippets are, behaviorally, almost completely equivalent. They both work and provide the same output, albeit they produce a slightly different bytecode.

def lower_case(s):
    return s.lower()

map(lower_case, ["A", "B"])

vs

def lower_case(s):
    return s.lower()

map(lambda s: lower_case(s), ["A", "B"])

Learning Rust, I'm trying to wrap my head around the following case. Having a function that recieves a string and returns a version of the string with the first character capitalized:

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(),
    }
}

Wrapping this function with another function that accepts a vector of strings is where this becomes interesting:

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(),
    }
}

pub fn capitalize_words(words: Vec<&str>) -> Vec<String> {
    words.iter().map(|w| capitalize_first(w)).collect::<Vec<String>>()
}

This works, but replacing

words.iter().map(|w| capitalize_first(w)).collect::<Vec<String>>()

with

words.iter().map(capitalize_first).collect::<Vec<String>>()

Causes the compilation to fail with the following error:

error[E0631]: type mismatch in function arguments
  --> exercises/standard_library_types/iterators2.rs:27:22
   |
12 | pub fn capitalize_first(input: &str) -> String {
   | ---------------------------------------------- found signature of `for<'r> fn(&'r str) -> _`
...
27 |     words.iter().map(capitalize_first).collect::<Vec<String>>()
   |                      ^^^^^^^^^^^^^^^^ expected signature of `fn(&&str) -> _`

error[E0599]: no method named `collect` found for struct `std::iter::Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> std::string::String {capitalize_first}>` in the current scope
   --> exercises/standard_library_types/iterators2.rs:27:40
    |
27  |     words.iter().map(capitalize_first).collect::<Vec<String>>()
    |                                        ^^^^^^^ method not found in `std::iter::Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> std::string::String {capitalize_first}>`
    |
   ::: C:\Users\Adi\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\src\libcore\iter\adapters\mod.rs:809:1
    |
809 | pub struct Map<I, F> {
    | -------------------- doesn't satisfy `_: std::iter::Iterator`
    |
    = note: the method `collect` exists but the following trait bounds were not satisfied:
            `<for<'r> fn(&'r str) -> std::string::String {capitalize_first} as std::ops::FnOnce<(&&str,)>>::Output = _`
            which is required by `std::iter::Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> std::string::String {capitalize_first}>: std::iter::Iterator`
            `for<'r> fn(&'r str) -> std::string::String {capitalize_first}: std::ops::FnMut<(&&str,)>`
            which is required by `std::iter::Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> std::string::String {capitalize_first}>: std::iter::Iterator`
            `std::iter::Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> std::string::String {capitalize_first}>: std::iter::Iterator`
            which is required by `&mut std::iter::Map<std::slice::Iter<'_, &str>, for<'r> fn(&'r str) -> std::string::String {capitalize_first}>: std::iter::Iterator`

Which I believe I understand.

However, following the suggestion to change

capitalize_first(input: &str)

to

capitalize_first(input: &&str)

passes compilation, but now tests are failing (obviously, because capitalize_first is called with &str, not &&str):

error[E0308]: mismatched types                                               
  --> exercises/standard_library_types/iterators2.rs:40:37                   
   |                                                                         
40 |         assert_eq!(capitalize_first("hello"), "Hello");                 
   |                                     ^^^^^^^ expected `&str`, found `str`
   |                                                                         
   = note: expected reference `&&str`                                        
              found reference `&'static str`                                 
                                                                             
error[E0308]: mismatched types                                               
  --> exercises/standard_library_types/iterators2.rs:45:37                   
   |                                                                         
45 |         assert_eq!(capitalize_first(""), "");                           
   |                                     ^^ expected `&str`, found `str`     
   |                                                                         
   = note: expected reference `&&str`                                        
              found reference `&'static str`                                 

Is there any compromise that will allow words.iter().map(capitalize_first).collect::<Vec<String>>() to work, while still allowing existing tests of capitalize_first to pass?

The difference between map(capitalize_first) and map(|x| capitalize_first(x)) is probably neglectable (visually, syntactically and performance-wise), but it is pretty annoying to define a closure that accepts an argument just to call a function with that same argument (some will even say it is an anti-pattern).

DeepSpace
  • 78,697
  • 11
  • 109
  • 154

1 Answers1

4

You can change capitalize_first to use a generic with the AsRef trait:

pub fn capitalize_first<T: AsRef<str>>(input: T) -> String {
    // use .as_ref() here to convert to &str
    let mut c = input.as_ref().chars();
    match c.next() {
        None => String::new(),
        Some(first) => first.to_uppercase().collect::<String>() + c.as_str(),
    }
}

This will make it compatible with both &str and &&str (and String and any number of nested references to str).

Aplet123
  • 33,825
  • 1
  • 29
  • 55