我正在学习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解决了我的问题。如果我使用稳定的编译器,我不会问这个问题。在这里,我遇到了意想不到的编译错误,因此我不会将其视为重复。
答案 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
的类型,我们基本上有两种可能:挖掘文档或让编译器告诉我们。让我们首先深入研究文档,然后用编译器确认知识。
所以counts
是HashMap<{integer}, {integer}>
,其中{integer}
只是某种整数:编译器仍然需要确切地找出哪个整数。如果没有给出更具体的信息(如在您的示例中),编译器默认为i32
整数。为了方便我们,让我们修复整数类型:
let mut counts: HashMap<i32, u32> = HashMap::new();
所以现在你写counts.iter()
...让我们通过查看the docs来检查这是做什么的:
pub fn iter(&self) -> Iter<K, V>
现在我们可以点击Iter
获取有关该类型的更多信息,也可以点击左侧的感叹号:
无论哪种方式,我们都看到了这个重要的影响:
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 = &i32
和v = &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
。
您可能会注意到,由于i32
为Copy
,我们还可以添加两个*
。
那是对问题的深入研究。简而言之:
(k, v)
不适合&(&{integer}, &{integer})
不兼容。因此您可以通过修复模式来解决问题。 答案 1 :(得分:2)
简而言之,请使用引用(playground)
let v = counts.iter().max_by_key(|&(_, v)| v);
总之,第一个示例有效,因为v
是可复制的,这意味着您将在闭包中获得v
的副本。 元组不可复制,这意味着元组将被移出hashmap,这是不允许的,这就是你必须使用引用的原因。