gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 05:45:46 26 4
gpt4 key购买 nike

我正在学习Rust,并且对所有权,借用和引用的概念相当满意。我已经达到了Rust Book第二版的第八章。

我正在使用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解决了我的问题。如果我使用的是稳定的编译器,我不会问这个问题。在这里,我遇到了意外的编译错误,因此我没有将其作为重复项关闭。

最佳答案

解决方案是添加一个&:

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})。因此,您可以通过修复模式来解决问题。
  • 在每晚 (带有RFC 2005 match ergonomics)上,您被编译器引入的附加引用层所困扰。这会导致生命周期错误。幸运的是,您不需要此其他引用,因此只需删除它即可。
  • 关于rust - Map上的max_by_key不允许将元组分解为键值对,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49997429/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com