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

Xol*_*lve 7 lifetime rust

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

我正在mode使用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);
}
Run Code Online (Sandbox Code Playgroud)

我得到以下编译器错误

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);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

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

更新1: 匹配元组作为地图的输入解决了我的问题.如果我使用稳定的编译器,我就不会问这个问题.在这里我遇到了意想不到的编译错误,因此我不会将其视为重复.

Luk*_*odt 11

解决方案是添加一个&:

counts.iter().max_by_key(|&(k, v)| v);
//                        ^
Run Code Online (Sandbox Code Playgroud)

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

counts.iter().max_by_key(|(k, v)| *v);
//                                ^
Run Code Online (Sandbox Code Playgroud)

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


那么为什么这样呢?

为了找到答案,让我们先来分析的类型x在这个片断(这是你的第一个版本,但我改名vx为清楚起见):

counts.iter().max_by_key(|x| x.1);
Run Code Online (Sandbox Code Playgroud)

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

那么counts一个HashMap<{integer}, {integer}>地方{integer}只是某种整数:编译器仍然需要确切地找出哪个整数.如果没有给出更多特定信息(如在您的示例中),则编译器默认i32为整数.为了方便我们,让我们修复整数类型:

let mut counts: HashMap<i32, u32> = HashMap::new();
Run Code Online (Sandbox Code Playgroud)

所以现在你写counts.iter()...让我们通过查看文档来检查它的作用:

pub fn iter(&self) -> Iter<K, V>
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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

impl<'a, K, V> Iterator for Iter<'a, K, V>
    type Item = (&'a K, &'a V);
Run Code Online (Sandbox Code Playgroud)

这告诉我们返回类型HashMap::iter()是一个迭代器,它产生类型的项(&K, &V)(引用的2元组).这里K是键类型(i32),Vu32哈希映射的值类型().所以我们的迭代器产生了类型的元素(&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, 
Run Code Online (Sandbox Code Playgroud)

它有点复杂,但不要担心!我们看到该方法需要(除了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 `()`
Run Code Online (Sandbox Code Playgroud)

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

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

counts.iter().max_by_key(|(k, v)| v);
Run Code Online (Sandbox Code Playgroud)

所以你在闭包的参数列表中使用了模式匹配.但是有人可能会想:等等,编译器如何将模式匹配(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)`
Run Code Online (Sandbox Code Playgroud)

您可以看到该模式&(k, v)适合&(&i32, &u32)(with k = &i32v = &u32).

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

那夜间错误怎么了?

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

match &(true, "hi".to_string()) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

由于&并且ref对许多人来说似乎很吵,Rust希望让这些情况更容易.跳过一些细节,锈病基本上自动的图案像转换(a, b)&(ref a, ref b)图案时,上一个引用类型使用.同样,这在一些情况下有所帮助,但也引入了一些意外的引用 - 比如在您的示例中:

counts.iter().max_by_key(|(k, v)| v);
Run Code Online (Sandbox Code Playgroud)

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

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

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

counts.iter().max_by_key(|&(k, v)| v);
Run Code Online (Sandbox Code Playgroud)

现在又v&i32(但是i32我们引用的值只要哈希映射就可以生存,所以一切都很好).或者我们可以通过添加以下内容来删除外部引用*:

counts.iter().max_by_key(|(k, v)| *v);
Run Code Online (Sandbox Code Playgroud)

这仍然使用夜间人体工程学改进,但删除外部参考,所以这*v也是&i32.

正如你可能会注意到,因为i32就是Copy我们还可以增加两个*.

摘要

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

  • 稳定时,您的模式与类型不兼容((k, v)不适合&(&{integer}, &{integer}).因此,您可以通过修复模式来解决问题.
  • 每晚(使用RFC 2005匹配人机工程学),您被编译器引入的附加参考层所困扰.这会导致终身错误.幸运的是,您不需要这个额外的参考,所以您可以简单地删除它.