在火柴臂中创建闭包

问题描述

我正在阅读 Rust Book 并一直在调整“minigrep”项目,这样我就有了一个 search 函数,而不是同时拥有 search_case_insensitivesearch 函数采用一个枚举指定大小写敏感。这就是我所拥有的:

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,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()reference 传递给迭代器提供的元素的结果,所以如果你迭代 &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),};