地图上的max_by_key不允许将元组解构为键值对

时间:2018-04-24 08:58:36

标签: rust lifetime

我正在学习Rust,并且对所有权,借用和引用的概念相当不错。我已达到Rust Book第二版的第8章。

我正在使用mode as given in an exercise实施map功能。我使用Iterator::max_by_key编写了以下实现:

use std::collections::HashMap;

fn main() {
    let vs = vec![0, 0, 1, 1, 3, 4, 5, 6, 3, 3, 3];

    let mut counts = HashMap::new();
    for num in vs {
        let count = counts.entry(num).or_insert(0);
        *count += 1;
    }

    // This works
    let u = counts.iter().max_by_key(|v| v.1);

    // This doesn't work
    let v = counts.iter().max_by_key(|(k, v)| v);
}

我收到以下编译错误

error[E0495]: cannot infer an appropriate lifetime for pattern due to conflicting requirements
  --> src/main.rs:16:43
   |
16 |     let v = counts.iter().max_by_key(|(k, v)| v);
   |                                           ^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 16:38...
  --> src/main.rs:16:38
   |
16 |     let v = counts.iter().max_by_key(|(k, v)| v);
   |                                      ^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:16:43
   |
16 |     let v = counts.iter().max_by_key(|(k, v)| v);
   |                                           ^
note: but, the lifetime must be valid for the method call at 16:13...
  --> src/main.rs:16:13
   |
16 |     let v = counts.iter().max_by_key(|(k, v)| v);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that a type/lifetime parameter is in scope here
  --> src/main.rs:16:13
   |
16 |     let v = counts.iter().max_by_key(|(k, v)| v);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

这个错误是什么意思以及为什么不允许这样做?

更新1: Match tuple as input to map解决了我的问题。如果我使用稳定的编译器,我不会问这个问题。在这里,我遇到了意想不到的编译错误,因此我不会将其视为重复。

2 个答案:

答案 0 :(得分:11)

解决方案是添加一个&

counts.iter().max_by_key(|&(k, v)| v);
//                        ^

...或(每晚)添加一个*

counts.iter().max_by_key(|(k, v)| *v);
//                                ^

它详细解释了如何找到自己的说明。如果您没有时间,最后会有摘要。

那为什么会这样呢?

为了找到答案,我们首先分析此代码段中x的类型(这是您的第一个版本,但为了清晰起见,我将v重命名为x ):

counts.iter().max_by_key(|x| x.1);

要检查x的类型,我们基本上有两种可能:挖掘文档或让编译器告诉我们。让我们首先深入研究文档,然后用编译器确认知识。

所以countsHashMap<{integer}, {integer}>,其中{integer}只是某种整数:编译器仍然需要确切地找出哪个整数。如果没有给出更具体的信息(如在您的示例中),编译器默认为i32整数。为了方便我们,让我们修复整数类型:

let mut counts: HashMap<i32, u32> = HashMap::new();

所以现在你写counts.iter() ...让我们通过查看the docs来检查这是做什么的:

pub fn iter(&self) -> Iter<K, V>

现在我们可以点击Iter获取有关该类型的更多信息,也可以点击左侧的感叹号:

enter image description here

无论哪种方式,我们都看到了这个重要的影响:

impl<'a, K, V> Iterator for Iter<'a, K, V>
    type Item = (&'a K, &'a V);

这告诉我们HashMap::iter()的返回类型是一个迭代器,它产生类型(&K, &V)的项(引用的2元组)。这里,K是密钥类型(i32),V是哈希映射的值类型(u32)。所以我们的迭代器产生(&i32, &u32)类型的元素。

好的!现在我们需要检查Iterator::max_by_key

fn max_by_key<B, F>(self, f: F) -> Option<Self::Item> 
where
    B: Ord,
    F: FnMut(&Self::Item) -> B, 

稍微复杂但不用担心!我们看到该方法需要(除了self)一个参数f: F。这是你传入的闭包。where子句告诉我们F: FnMut(&Self::Item)意味着F是一个函数,它有一个&Self::Item类型的参数。

但我们已经知道迭代器的Self::Item是什么:(&i32, &u32)。因此&Self::Item(附加参考)是 &(&i32, &u32) !这是闭包参数的类型,因此是x的类型。

让我们检查一下我们的研究是否正确。您可以通过强制执行类型错误,轻松指示编译器告诉您变量x的类型。让我们通过添加表达式x == ()来做到这一点。在这里,我们尝试将您的变量与()进行比较,后者永远无效。事实上我们得到了错误:

14 |         x == ();
   |           ^^ can't compare `&(&i32, &u32)` with `()`

成功!我们正确找到了x的类型。那么这对我们有什么帮助呢?

在第二个例子中,您写道:

counts.iter().max_by_key(|(k, v)| v);

所以你在闭包的参数列表中使用了模式匹配。但有人可能会想:等等,编译器如何将模式(k, v)&(&i32, &u32)类型匹配?一开始有一个参考不适合!

这正是稳定编译器上发生的事情:

error[E0658]: non-reference pattern used to match a reference (see issue #42640)
  --> src/main.rs:18:39
   |
18 |     counts.iter().max_by_key(|(k, v)| v);
   |                               ^^^^^^ help: consider using a reference: `&(k, v)`

您可以看到模式&(k, v)确实适合&(&i32, &u32)k = &i32v = &u32)。

所以谈论稳定的编译器,你的问题只是你的模式不适合预期的类型。

那么夜间错误是什么?

最近,一些符合人体工程学的改进落在了Rust(仅限夜间),这有助于减少常见情况下的噪音。 RFC 2005提出了这一特殊改进。这种常见的情况是匹配元组的引用并希望获得对元素的引用,就像我们匹配类型&(bool, String)的情况一样:

match &(true, "hi".to_string()) {
    // ...
}

因此,如果不考虑引用,可​​能会使用模式(b, s)(类似于您对(k, v)所做的那样)。但这并不起作用(稳定),因为模式不合适(它缺少参考)。

所以相反,模式&(b, s)起作用 - 至少是那种。因为虽然模式与类型匹配,但现在s具有类型String,因此试图移出不允许的原始元组(因为我们只有对它的引用)。

所以你写的是:&(b, ref s)。现在s的类型&String很好。

由于&ref对许多人来说似乎很吵,所以Rust希望让这些情况更容易。跳过一些细节,当模式用于引用类型时,Rust基本上会自动将(a, b)之类的模式转换为&(ref a, ref b)。同样,这在一些情况下有所帮助,但也引入了一些意想不到的引用 - 比如在您的示例中:

counts.iter().max_by_key(|(k, v)| v);

正如我们所看到的,模式(k, v)实际上并不适合该类型,但Rust会应用规则并将您的模式转换为&(ref k, ref v)。现在模式匹配有效,但我们还有另一个问题:

现在v&&u32:对参考的引用! (要了解为什么会出现这种情况,只需仔细检查我们上面讨论过的所有类型。)但是内部引用只有迭代器才会存在,所以我们无法返回它并且yada yada生存问题。简单的解决方案就是删除外部参考,因为我们不需要它。

我们通过使模式显式化(并使其在稳定下工作)来实现这一目标:

counts.iter().max_by_key(|&(k, v)| v);

现在v再次&i32(但我们引用的i32值与哈希映射一样长,所以一切都很好)。或者我们可以通过添加*

来删除外部引用
counts.iter().max_by_key(|(k, v)| *v);

这仍然使用夜间符合人体工程学的改进,但删除了外部参考,因此*v也是&i32

您可能会注意到,由于i32Copy,我们还可以添加两个*

摘要

那是对问题的深入研究。简而言之:

  • 稳定上,您的模式与类型((k, v)不适合&(&{integer}, &{integer})不兼容。因此您可以通过修复模式来解决问题。
  • nightly (使用RFC 2005 match ergonomics)上,您被编译器引入的其他参考图层所咬。这会导致终身错误。幸运的是,你不需要这个额外的参考,所以你可以简单地删除它。

答案 1 :(得分:2)

简而言之,请使用引用(playground

let v = counts.iter().max_by_key(|&(_, v)| v);

总之,第一个示例有效,因为v是可复制的,这意味着您将在闭包中获得v的副本。 元组不可复制,这意味着元组将被移出hashmap,这是不允许的,这就是你必须使用引用的原因。