web-dev-qa-db-ja.com

ハッシュマップを反復処理し、キー/値を出力し、Rustの値を削除する方法は?

これはどの言語でも簡単な作業です。これは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

なぜ参照を移動しようとしているのですか?ドキュメントから、参照に移動/借入が適用されるとは思わなかった。

16
adapt-dev

これが許可されない少なくとも2つの理由があります。

  1. mapへの2つの同時可変参照が必要です。1つはforループで使用されるイテレーターによって保持され、もう1つは_map.remove_を呼び出す変数mapで保持されます。 。

  2. マップを変更しようとすると、キーと値への参照withinがあります。何らかの方法でマップの変更が許可された場合、これらの参照が無効になり、メモリの安全性が失われる可能性があります。

コアRust原則はエイリアシングXOR Mutability。複数の不変値への参照、または単一の可変参照を使用できます。

移動/借入が参照に適用されるとは思わなかった。

すべてのタイプは、Rustの移動規則と可変エイリアスの規則に従います。ドキュメントのどの部分がそうではないと言っているのか教えてください。

なぜ参照を移動しようとしているのですか?

これは2つの部分で構成されています。

  1. 単一の可変参照のみを持つことができます
  2. forループ 値で反復する値を取得

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();
}
_

文字「A」で始まるキーを持つすべての値を削除します

私は _HashMap::retain_ を使用します:

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

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

これにより、マップが実際に変更されたときにkeyvalueが存在しないことが保証されるため、それらが持っていたはずの借用がなくなります。

15
Shepmaster

これはどの言語でも簡単な作業です。

Rustは、マップを変更することを妨げていますwhileほとんどの言語ではこれが許可されていますが、多くの場合、動作は明確に定義されておらず、アイテムを削除すると反復が妨げられ、正確さが損なわれます。

なぜ参照を移動しようとしているのですか?

HashMapIntoIteratorを実装します。 つまり、ループは同等です

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

into_iterの定義 を見ると、&self&mut selfではなく、selfが必要であることがわかります。変数mapは参照であるため、selfを取得するために暗黙的に逆参照されます。そのため、エラーは*mapが移動されたことを示しています。

APIはそのように意図的に構築されているため、構造をループしている間は危険なことはできません。ループが完了すると、構造の所有権は放棄され、再び使用できます。

1つの解決策は、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のことを忘れていました。それはより簡潔で、私がやったように不必要なコピーをしません。

6
Peter Hall