在火柴臂中创建闭包

Creating closures in match arms

我正在阅读 Rust Book 并一直在调整“minigrep”项目,这样我就没有同时拥有 searchsearch_case_insensitive 功能,而是拥有一个 search采用指定区分大小写的枚举的函数。这就是我的作品:

pub fn search<'a>(query: &str, contents: &'a str, case: &Case) -> Vec<&'a str> {
    match case {
        Case::Sensitive => contents
            .lines()
            .filter(|line| line.contains(query))
            .collect(),

        Case::Insensitive => {
            let query = query.to_lowercase();

            contents
                .lines()
                .filter(|line| line.to_lowercase().contains(&query))
                .collect()
        }
    }
}

我想我会尝试重写它,以使用闭包删除重复的逻辑。然而,这会导致各种类型检查问题。我的第一次尝试:

let query_lower = query.to_lowercase();

let filter = match case {
   Case::Sensitive => |line| line.contains(query),
   Case::Insensitive => |line| line.to_lowercase().contains(&query_lower)
};

我很快了解到不同的闭包具有不同的类型,即使它们具有相同的签名。我读到装箱关闭可能会有所帮助:

let filter: Box<dyn Fn(&str) -> bool> = match case {
    Case::Sensitive => Box::new(|line| line.contains(query)),
    Case::Insensitive => Box::new(|line| line.to_lowercase().contains(&query_lower))
};

这当然解决了 match 手臂类型不匹配的问题,但现在 .filter 抱怨它的参数类型不正确,现在我陷入困境:

error[E0277]: expected a `FnMut<(&&str,)>` closure, found `dyn for<'r> Fn(&'r str) -> bool`
  --> src/lib.rs:27:17
   |
27 |         .filter(filter)
   |                 ^^^^^^ expected an `FnMut<(&&str,)>` closure, found `dyn for<'r> Fn(&'r str) -> bool`
   |
   = help: the trait `FnMut<(&&str,)>` is not implemented for `dyn for<'r> Fn(&'r str) -> bool`
   = note: required because of the requirements on the impl of `for<'r> FnMut<(&'r &str,)>` for `Box<dyn for<'r> Fn(&'r str) -> bool>`

以这种方式使用闭包不是一个好主意吗,因为类型规则,或者我只是出于天真而错误地“装箱”了东西?

filter 闭包的类型必须是 FnMut(&Self::Item) -> bool 并且因为你有一个 Iterator<Item = &str>,所以 &Self::Item 变成了 &&str。所以你应该使用 Box<dyn Fn(&&str) -> bool:

而不是 Box<dyn Fn(&str) -> bool>
enum Case {
    Sensitive,
    Insensitive,
}

fn search<'a>(query: &str, contents: &'a str, case: &Case) -> Vec<&'a str> {
    let query_lower = query.to_lowercase();

    let filter: Box<dyn Fn(&&str) -> bool> = match case {
        Case::Sensitive => Box::new(|line| line.contains(query)),
        Case::Insensitive => Box::new(|line| line.to_lowercase().contains(&query_lower)),
    };

    contents.lines().filter(filter).collect()
}

playground

这是可行的,因为所有 Fn 类型也是 FnMut

你们很接近。该错误是 Iterator::filter() 引用 传递给迭代器提供的元素的结果,因此如果您迭代 &str,闭包传递给 filter 必须接受 &&str。只需将 Box<dyn Fn(&str) -> bool> 更改为 Box<dyn Fn(&&str) -> bool> 即可修复要编译的示例。

您可以通过使用两个变量和一个动态引用来避免不必要的分配:

let case_sensitive_filter = |line: &&str| line.contains(query);
let case_insensitive_filter = |line: &&str| line.to_lowercase().contains(&query.to_lowercase());
let filter: &dyn Fn(&&str) -> bool = match case {
    Case::Sensitive => &case_sensitive_filter,
    Case::Insensitive => &case_insensitive_filter,
};
contents.lines().filter(filter).collect()

解决武器不兼容问题的另一种方法是创建一个闭包并检查闭包内的案例:

let filter = |line: &&str| match case {
    Case::Sensitive => line.contains(query),
    Case::Insensitive => line.to_lowercase().contains(&query_lower),
};