如何捕获Rust中闭包范围之外的变量?

时间:2016-03-26 13:12:38

标签: closures rust

我正在写一个不区分大小写的anagram finder,给出一个单词和一个单词列表。我有以下代码:

pub fn anagrams_for(s: &'static str, v: &[&'static str]) -> Vec<&'static str> {
    let mut s_sorted: Vec<_> = s.to_lowercase().chars().collect();
    s_sorted.sort();

    v.iter().filter_map(move |word: &str| {
        let mut word_sorted: Vec<_> = word.to_lowercase().chars().collect();
        word_sorted.sort();

        if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
            Some(word)
        } else {
            None
        }
    }).collect()
}

这样做的逻辑是对给定单词的小写进行排序,对于向量中的每个单词,执行相同的操作。如果单词预先排序不同(消除自我字谜)但是后排序相同,则将其添加到输出中。

上述似乎在从周围范围捕获ss_sorted时遇到问题,因为在编译时我收到以下错误:

  

错误:类型不匹配:类型[closure@src/lib.rs:23:25: 32:6 s_sorted:_, s:_]实现了特征for<'r> core::ops::FnMut<(&'r str,)>,但特征core::ops::FnMut<(&&str,)>是必需的(预期&amp; -ptr,找到str)

当我查看此错误类型的描述([E0281])时,我发现了以下TL; DR:

  

在这种情况下的问题是foo被定义为接受Fn而没有   参数,但我们试图传递给它的闭包需要一个参数。

这令人困惑,因为我认为来自周围范围的move closures capture variables

我错过了什么?

2 个答案:

答案 0 :(得分:3)

这与在闭包中捕获变量没有任何关系。让我们再次查看错误消息,重新格式化一下:

type mismatch:
    the type `[closure@<anon>:5:25: 14:6 s_sorted:_, s:_]`
    implements the trait `for<'r> core::ops::FnMut<(&'r str,)>`,
    but the trait `core::ops::FnMut<(&&str,)>` is required
    (expected &-ptr, found str)

更清楚的是:

found:    for<'r> core::ops::FnMut<(&'r str,)>
expected:         core::ops::FnMut<(&&str,)>

进一步放大:

found:    &'r str
expected: &&str

罪魁祸首是:|word: &str|

您已声明您的闭包接受字符串切片,但不是迭代器产生的v&str的切片,切片上的迭代器返回对切片中项目的引用。每个迭代器元素都是&&str

将关闭更改为|&word|,它会起作用。在值绑定到word之前,这使用模式匹配来取消引用闭包参数。等价(但不太惯用),您可以在闭包内使用|word|然后*word

此外...

  1. 您不需要将自己限制在'static字符串:

    pub fn anagrams_for<'a>(s: &str, v: &[&'a str]) -> Vec<&'a str> {
    
  2. 不需要move关闭。

  3. 提取创建排序字符向量的逻辑。这有助于确保逻辑在两个之间保持一致意味着您不必将这些向量声明为可变的超过需要的时间。
  4. fn sorted_chars(s: &str) -> Vec<char> { 
        let mut s_sorted: Vec<_> = s.to_lowercase().chars().collect();
        s_sorted.sort();
        s_sorted
    }
    
    pub fn anagrams_for<'a>(s: &str, v: &[&'a str]) -> Vec<&'a str> {
        let s_sorted = sorted_chars(s);
    
        v.iter().filter_map(|&word| {
            let word_sorted = sorted_chars(word);
    
            if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
                Some(word)
            } else {
                None
            }
        }).collect()
    }
    
    fn main() {}
    

答案 1 :(得分:3)

此处的问题是您尝试从Vec<&'static str>的序列中创建&&'static str

pub fn anagrams_for(s: &'static str, v: &[&'static str]) -> Vec<&'static str> {
    let mut s_sorted: Vec<_> = s.to_lowercase().chars().collect();
    s_sorted.sort();

    v.iter().cloned().filter_map(|word: &'static str| {
        let mut word_sorted: Vec<_> = word.to_lowercase().chars().collect();
        word_sorted.sort();

        if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
            Some(word)
        } else {
            None
        }
    }).collect()
}

cloned&&'static str&'static str来电是必要的。这是一个廉价的操作,因为&str只是指向某个utf8序列的指针,加上一个长度。

编辑:实际上更好的解决方案是尽可能晚地克隆

v.iter().filter_map(move |word: &&'static str| { // <--- adapted the type to what is actually received
    let mut word_sorted: Vec<_> = word.to_lowercase().chars().collect();
    word_sorted.sort();

    if word_sorted == s_sorted && s.to_lowercase() != word.to_lowercase() {
        Some(word.clone()) // <--- moved clone operation here
    } else {
        None
    }
}).collect()