【问题标题】:How do I capture variables outside the scope of a closure in Rust?如何在 Rust 中捕获闭包范围之外的变量?
【发布时间】:2016-03-26 13:12:38
【问题描述】:

我正在编写一个不区分大小写的字谜查找器,给定一个单词和一个单词列表。我有以下代码:

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&lt;'r&gt; core::ops::FnMut&lt;(&amp;'r str,)&gt;,但特征core::ops::FnMut&lt;(&amp;&amp;str,)&gt; 是必需的(预期为&-ptr,找到str)

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

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

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

我错过了什么?

【问题讨论】:

    标签: closures rust


    【解决方案1】:

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

    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: &amp;str|

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

    将您的关闭更改为|&amp;word|,它将起作用。这使用模式匹配在值绑定到word 之前取消引用闭包参数一次。等效地(但不那么惯用),您可以在闭包内使用 |word|*word


    另外...

    1. 您无需将自己限制为'static 字符串:

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

    3. 提取创建排序字符向量的逻辑。这有助于确保逻辑在两个 之间保持一致,这意味着您不必将这些向量声明为可变的时间超过所需的时间。
    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() {}
    

    【讨论】:

    • 我怀疑通过引用word,这将比克隆迭代器快得多。您认为继续使用移动闭包还是使用其他答案更好(这会更改闭包参数的类型并克隆迭代器)?
    • @erip 比克隆迭代器 — 克隆的不是整个迭代器;它只是一个额外的适配器,可以在生成每个元素时对其进行克隆。我的希望和直觉是这两种解决方案的性能相同。 相信这种解决方案在这种情况下会更惯用。请注意,无论如何您都不需要 move 闭包(我已更新)。
    • 感谢您的提示。非常有用且全面。
    • 我也认为这更惯用。尽可能删除类型注释......(IMO:3)
    • 关于最后一点:您也可以将附加的sorted_chars 函数定义移动到另一个函数中。避免全局命名空间混乱。
    【解决方案2】:

    这里的问题是您试图从&amp;&amp;'static str 的序列中创建一个Vec&lt;&amp;'static str&gt;

    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 调用是从&amp;&amp;'static str 转到&amp;'static str 所必需的。这是一个廉价的操作,因为 &amp;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()
    

    【讨论】:

    • 嗯,这很愚蠢。你介意补充一下为什么需要cloned 调用作为编辑吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-11
    • 1970-01-01
    • 1970-01-01
    • 2019-05-31
    • 1970-01-01
    • 2011-12-06
    相关资源
    最近更新 更多