2017-08-16 111 views
6

這應該是任何語言的一項簡單任務。這不適用於Rust。如何遍歷Hashmap,打印鍵/值並刪除Rust中的值?

use std::collections::HashMap; 

fn do_it(map: &mut HashMap<String, String>) { 
    for (key, value) in map { 
     println!("{}/{}", key, value); 
     map.remove(key); 
    } 
} 

fn main() {} 

這裏的編譯器錯誤:

error[E0382]: use of moved value: `*map` 
--> src/main.rs:6:9 
    | 
4 |  for (key, value) in map { 
    |       --- value moved here 
5 |   println!("{}/{}", key, value); 
6 |   map.remove(key); 
    |   ^^^ value used here after move 
    | 
    = note: move occurs because `map` has type `&mut std::collections::HashMap<std::string::String, std::string::String>`, which does not implement the `Copy` trait 

爲什麼它試圖移動的參考?從文檔中,我不認爲移動/借用適用於參考。

回答

9

至少有兩個原因,這是不允許的:

  1. 你需要有兩個並行的可變引用map - 一個由變量mapfor環和一個使用的iterator舉行致電map.remove

  2. 當您試圖改變地圖時,您有引用密鑰和值地圖。如果允許您以任何方式修改地圖,這些參考可能會失效,從而打開記憶不安全的大門。

一個核心原則鏽是別名XOR可變性。你可以對一個值有多個不可變的引用,或者你可以有一個可變的引用。

I didn't think moving/borrowing applied to references.

每種類型都受Rust的移動規則以及可變的鋸齒。請讓我們知道文檔的哪一部分不是這樣,我們可以解決這個問題。

Why it is trying to move a reference?

這是結合兩個部分組成:

  1. 您只能有一個可變的引用
  2. for循環take the value to iterate over by value

當你調用for (k, v) in map {}map的所有權轉移到for循環,現在消失了。


我會執行一個不可變的地圖借用(&*map)並迭代它。最後,我想清楚整個事情:

fn do_it(map: &mut HashMap<String, String>) { 
    for (key, value) in &*map { 
     println!("{}/{}", key, value); 
    } 
    map.clear(); 
} 

remove every value with a key that starts with the letter "A"

我會使用HashMap::retain

fn do_it(map: &mut HashMap<String, String>) { 
    map.retain(|key, value| { 
     println!("{}/{}", key, value); 

     !key.starts_with("a") 
    }) 
} 

這保證了keyvalue當地圖實際上是修改不復存在,因此他們所有的借款現在都沒有了。

+0

我可以通過映射{}中的for(key,value)得到同樣的錯誤;對於map {}中的(鍵,值),我不認爲這個答案解釋了這一點。 –

+1

想一想,如果你在循環中調用'map.clear()'會發生什麼? 'key'和'value'是引用,它們不會再引用任何東西。 'clear'和'remove'都使用'&mut self',從借用檢查器的角度來看,它們是相同的。 – loganfsmyth

+0

這使我碰到了一個偶然的問題,但我懷疑方法調用語法掩蓋了這個問題。 https://play.rust-lang.org/?gist=ecf6d9bdbe8e1ad99e5fb3c35c402d1c&version=stable –

5

This should be a trivial task in any language.

鏽阻止您突變地圖你迭代它。在大多數語言中這是允許的,但通常這種行爲並沒有很好的定義,刪除該項目會干擾迭代,損害其正確性。

Why it is trying to move a reference?

HashMap實現IntoIteratorso your loop is equivalent to

for (key, value) in map.into_iter() { 
    println!("{}/{}", key, value); 
    map.remove(key); 
} 

如果你看一下definition of into_iter,你會看到,它需要self,不&self&mut self。你的變量map是一個參考,所以它隱含地取消了self,這就是爲什麼錯誤說*map已被移動。

這個API是特意構建的,所以在循環結構時你不能做任何危險的事情。一旦循環完成,結構的所有權將被放棄,您可以再次使用它。

一種解決方案是保持跟蹤你打算在Vec刪除的項目,然後事後除去它們:

fn do_it(map: &mut HashMap<String, String>) { 
    let mut to_remove = Vec::new(); 
    for (key, value) in &*map { 
     if key.starts_with("A") { 
      to_remove.push(key.to_owned()); 
     } 
    } 
    for key in to_remove.iter() { 
     map.remove(key); 
    } 
} 

你也可以使用一個迭代地圖過濾到一個新的。也許是這樣的:

fn do_it(map: &mut HashMap<String, String>) { 
    *map = map.into_iter().filter_map(|(key, value)| { 
     if key.starts_with("A") { 
      None 
     } else { 
      Some((key.to_owned(), value.to_owned())) 
     } 
    }).collect(); 
} 

但我剛纔看到Shepmaster的編輯 - 我已經忘記了retain,這是更好的。它更簡潔,不會像我所做的那樣進行不必要的複製。