【问题标题】:Creating closures in match arms在匹配臂中创建闭包
【发布时间】:2021-05-09 00:31:51
【问题描述】:

我正在阅读 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>`

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

【问题讨论】:

    标签: types rust pattern-matching closures boxing


    【解决方案1】:

    filter 闭包的类型必须是 FnMut(&amp;Self::Item) -&gt; bool,因为你有一个 Iterator&lt;Item = &amp;str&gt;,所以 &amp;Self::Item 变成 &amp;&amp;str。所以你应该使用Box&lt;dyn Fn(&amp;&amp;str) -&gt; bool,而不是Box&lt;dyn Fn(&amp;str) -&gt; bool&gt;

    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

    【讨论】:

      【解决方案2】:

      你已经很接近了。错误是 Iterator::filter()reference 传递给迭代器提供的元素的结果,因此如果您正在迭代 &amp;str,则传递给 filter 的闭包必须接受 &amp;&amp;str。只需将 Box&lt;dyn Fn(&amp;str) -&gt; bool&gt; 更改为 Box&lt;dyn Fn(&amp;&amp;str) -&gt; bool&gt; 即可修复要编译的示例。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-06-01
        • 1970-01-01
        • 2015-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多