使用iter().map()的函数 - 作为命名函数和作为闭包的区别

6

如果你熟悉Python,那么这两段代码在行为上几乎是完全等价的。它们都能正常工作并提供相同的输出,尽管它们会生成稍微不同的字节码。

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

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

对比

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

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

学习 Rust,我试图理解以下情况。有一个函数接收一个字符串,并返回将字符串的第一个字符大写化的版本:
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_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>>()
}

这个方案可行,但是替换

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

使用

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

导致编译失败,并显示以下错误:
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`

我相信我理解了。

然而,遵循建议进行更改后

capitalize_first(input: &str)

to

capitalize_first(input: &&str)

编译通过,但是现在测试失败了(显然是因为 capitalize_first 被调用时使用的是 &str,而不是 &&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`                                 

有没有一种妥协方案,可以让words.iter().map(capitalize_first).collect::<Vec<String>>()这个语句可用,同时还能让现有的对capitalize_first的测试通过?

map(capitalize_first)map(|x| capitalize_first(x))之间的区别可能微不足道(从视觉、语法和性能上看),但为了调用带有同样参数的函数而定义一个接受参数的闭包实在是相当烦人(有些人甚至会说这是一种反模式)。

1个回答

4
你可以使用具有 AsRef 特征的通用类型来更改 capitalize_first
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(),
    }
}

这样可以使其兼容 &str&&str(以及 String 和任意嵌套引用的 str)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接