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

brc7rcf0  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(236)

我正在学习Rust,并且对所有权,借用和引用的概念相当好。我已经达到了Rust书第二版的Chido8。
我在练习中使用map实现mode函数。我使用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解决了我的问题。如果我使用的是稳定的编译器,我就不会问这个问题。这里我得到了意外的编译错误,所以我不会将其作为重复关闭。
ntjbwcob

ntjbwcob1#

编辑:自Rust 2018以来,由于“匹配人体工程学”,这应该可以工作。
解决方案是添加一个&

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的类型,我们基本上有两种可能性:通过文档挖掘或让编译器告诉我们。让我们先通过文档挖掘,然后与编译器确认知识。
所以counts是一个HashMap<{integer}, {integer}>,其中{integer}只是一种整数:编译器仍然需要找出确切的整数。如果没有更多的具体信息(如在你的例子中),编译器默认为整数i32。为了让我们更容易,让我们修复整数类型:

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


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

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


现在,我们可以单击Iter以获取有关该类型的更多信息,或者单击左侧的感叹号:
x1c 0d1x的数据
无论如何,我们看到这个重要的impl:

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


这告诉我们HashMap::iter()的返回类型是一个迭代器,它产生(&K, &V)类型的元素(一个2元组的引用)。这里,K是键类型(i32),V是哈希Map的值类型(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:一个对引用的引用!(要知道为什么会这样,只要仔细检查我们上面讨论过的所有类型就可以了。)但是内部引用只存在于迭代器中,所以我们不能返回它,也不能解决生存期问题。简单的解决方案是简单地删除外部引用,因为我们不需要它。

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

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


现在v又是&i32了(但是我们引用的i32值和哈希Map一样长,所以一切都很好)。或者我们可以通过添加*来删除外部引用:

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


这仍然使用了夜间人体工程学的改进,但删除了外部引用,因此*v也是&i32
您可能注意到,由于i32Copy,因此我们也可以添加两个*

摘要

好吧,这是一个深入的问题。简而言之:

  • stable上,您的模式与类型不兼容((k, v)不适合&(&{integer}, &{integer})。因此,您可以通过修复模式来修复问题。
  • nightly(使用RFC 2005 match ergonomics)上,编译器引入了一个额外的引用层。这会导致生存期错误。幸运的是,您不需要这个额外的引用,所以您可以简单地删除它。
oaxa6hgo

oaxa6hgo2#

简而言之,使用引用(playground

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

字符串
在long中,第一个例子是可行的,因为v是可复制的,这意味着你将在闭包中获得v的副本。元组是不可复制的,这意味着元组将被移出hashmap,这是不允许的,这就是为什么你必须使用引用。

相关问题