遍历 BTreeMap 和 &BTreeMap 的区别

Difference between Iterating over BTreeMap and &BTreeMap

我正在尝试了解

之间的区别
let rows = Vec::new();
for (k, v) in my_btree { // BTreeMap<i64, String>
    rows.push((&k, &v)) // k and v don't live long enough.
}

和:

let rows = Vec::new();
for (k, v) in &my_btree { // BTreeMap<i64, String>
    rows.push((k, v))
}

有人可以解释迭代 &my_btree 和迭代 my_btree 之间的区别吗?

具体来说,我想了解所有权是如何变化的,以及在上面的两个例子中指的是什么内存

这是我正在尝试做的事情的完整示例(target_function 是我正在使用的库,它具有与此处相同的函数签名,因此无法更改):

use std::collections::BTreeMap;

struct SomeStruct {
    x: BTreeMap<i64, String>
}

fn target_function(rows: &[(&i64, &String)]) {
    for row in rows.iter() {
        println!("{:#?}", row);
    }    
}
fn test(ss: SomeStruct) {
    let mut rows = Vec::new();

    for (k, v) in &ss.x {
        rows.push((k, v));
    }
    target_function(&rows[..]);
}

fn main() {
    let mut a = BTreeMap::new();
    a.insert(1, "hello".to_string());
    a.insert(2, "goodbye".to_string());

    let mystruct = SomeStruct{x: a};
    test(mystruct);
}

微妙之处在于 IntoIteratorBTreeMap 的双重实现(实际上是大多数集合)。可以在documentation中看到,顺序是:

  • impl<K, V> IntoIterator for BTreeMap<K, V>

这是您的第一个案例。您正在做的是移动 BTreeMap 并使用它来生成 (K, V) 迭代器。您可以使用以下代码段说服自己:

let mut my_btree:BTreeMap<i64, String> = BTreeMap::new();
my_tree.insert(3, "this is a test".to_string());
let mut rows = Vec::new();
for (k, v) in my_btree { // BTreeMap<i64, String>
    rows.push((k, v))
}

这会消耗 my_btree 并产生 (K, V) 对,一个接一个。由于这些对是 拥有的 ,您可以安全地将它们推入您提供的 Vec。在您的代码段中,您有 &k&v - 这些引用永远不会起作用,因为这些项目会立即从范围中删除。

  • impl<'a, K, V> IntoIterator for &'a BTreeMap<K, V>

这是您的第二个案例。在这种情况下,您的迭代器现在是 (&'a K, &'a V),您可以通过尝试将 Vec 移出 BTreeMap 的范围来轻松地让自己相信这一点,如下所示:

fn does_not_work<'a>() -> Vec<(&'a i64, &'a String)> {
    let my_btree:BTreeMap<i64, String> = BTreeMap::new();
    let mut rows = Vec::new();
    for (k, v) in &my_btree { // BTreeMap<i64, String>
        rows.push((k, v))
    }
    rows
}

这不会编译,因为您在 BTreeMap 中插入了一堆对元素的引用,然后您将其删除(由于它移出范围)- 如果借用,所有这些引用都将无效检查员没有来救援。

所以,这就是区别 - 在一种情况下,您使用 BTreeMap 来操作拥有的结构,而在另一种情况下,您正在处理引用。

您的示例因此起作用 - 由于 &ss.x 迭代器从不使用地图,您实际上从未取消引用地图。