【问题标题】:max_by_key on Map doesn't allow destructuring of tuple into key-value pairMap 上的 max_by_key 不允许将元组解构为键值对
【发布时间】:2018-10-04 11:08:16
【问题描述】:

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

我正在使用map as given in an exercise 实现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 解决了我的问题。如果我使用的是稳定的编译器,我就不会问这个问题。在这里我遇到了意外的编译错误,所以我不会将其作为重复项关闭。

【问题讨论】:

  • 您使用的是夜间编译器,不是吗?如果你切换到稳定版,你可以看到这个问题的实际修复。
  • 哇!我使用了稳定的编译器,它给了我一个直接的修复。我怎么知道以后会这样?
  • @ljedrz 我认为我们不应该将其作为重复项关闭,因为这可能是夜间编译器错误的第一个问题(这些符合人体工程学的改进......)。

标签: rust lifetime


【解决方案1】:

解决方案是添加单个&

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

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

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

后面有详细的解释,说明如何找出自己。没时间的话,最后有总结。


那么为什么会这样呢?

为了搞清楚,我们先分析一下这个sn-p中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:

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

这告诉我们HashMap::iter() 的返回类型是一个迭代器,它产生(&amp;K, &amp;V) 类型的项目(引用的2 元组)。这里,K 是键类型(i32),V 是哈希映射的值类型(u32)。所以我们的迭代器产生(&amp;i32, &amp;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(&amp;Self::Item) 意味着F 是一个函数,它有一个&amp;Self::Item 类型的参数。

但我们已经知道迭代器的Self::Item 是什么:(&amp;i32, &amp;u32)。所以&amp;Self::Item(加上引用)是&amp;(&amp;i32, &amp;u32)!这是闭包参数的类型,因此也是 x 的类型。

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

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

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

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

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

所以你在闭包的参数列表中使用了模式匹配。但有人可能会想:等等,编译器怎么能将模式(k, v) 匹配到类型&amp;(&amp;i32, &amp;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)`

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

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

那么夜间错误是怎么回事?

最近,Rust 中出现了一些符合人体工程学的改进(仍然仅限每晚),这有助于减少常见情况下的嘈杂代码。这个特殊的改进是在RFC 2005 中提出的。这种常见的情况是匹配元组的引用并希望获得对元素的引用,就像在这种情况下我们匹配 &amp;(bool, String) 类型:

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

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

因此,&amp;(b, s) 的模式是可行的——至少是这样。因为虽然模式与类型匹配,但现在s 具有类型String,因此正试图移出不允许的原始元组(因为我们只有对它的引用)。

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

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

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

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

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

我们通过明确我们的模式(并使其稳定工作)来实现这一点:

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

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

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

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

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

总结

嗯,这是对问题的深入探讨。简而言之:

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

【讨论】:

  • 谢谢@Lukas 我明白我哪里出错了!非常感谢您提供详细和图解的解释。它清除了很多东西。
【解决方案2】:

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

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

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

【讨论】:

  • 太好了! let u = counts.iter().max_by_key(|v| v.1); 中的v 是什么类型。不是元组吗?
  • 该死!你咳了咳。我不知道正确的术语,但让我试着澄清一下。 v 是可复制的(是的,它是一个元组),因为它的每个元素都是可复制的(可复制只是意味着我们可以做一个 memcpy(参见stackoverflow.com/questions/31012923/…))另一个是不可复制的未打包元组(尽管元素是) .我不能正确地告诉你为什么这个 bahaves 那样,但确实如此:D 也许有人可以解释得更好,抱歉。
  • 感谢@hellow 请检查 Lukas 的答案以获得详细解释!
猜你喜欢
  • 1970-01-01
  • 2016-03-14
  • 2019-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多