あっぽログ
← 記事一覧に戻る

RustのHashMapを使いこなす:基本操作からエントリAPIまで

HashMapとは?

Rustの標準ライブラリには、キーと値のペアでデータを管理できる HashMap というコレクション型が用意されています。
JavaScriptのオブジェクト、PythonのDictionaryに相当するもので、素早いデータの検索・追加・削除に役立ちます。

この記事では、HashMapの基本的な使い方から、Rustならではの エントリAPI を使った高度な操作までを丁寧に解説します。


HashMapを使う準備

HashMapは std::collections モジュールに含まれています。使用する前に use 宣言が必要です。

use std::collections::HashMap;

基本操作

作成と値の挿入

HashMap::new() で空のHashMapを作成し、insert() でキーと値を追加します。

use std::collections::HashMap;

fn main() {
    let mut scores: HashMap<String, i32> = HashMap::new();

    scores.insert(String::from("Alice"), 100);
    scores.insert(String::from("Bob"), 85);
    scores.insert(String::from("Charlie"), 92);

    println!("{:?}", scores);
    // {"Alice": 100, "Bob": 85, "Charlie": 92} のように出力
}

ポイント: HashMapは順序を保証しません。イテレートするたびに順番が変わる場合があります。


値の取得

get() メソッドでキーに対応する値を取得します。戻り値は Option<&V> 型です。

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Alice"), 100);

    // getはOption<&i32>を返す
    if let Some(score) = scores.get("Alice") {
        println!("Aliceのスコア: {}", score); // Aliceのスコア: 100
    }

    // 存在しないキーはNoneを返す
    match scores.get("Dave") {
        Some(s) => println!("Dave: {}", s),
        None    => println!("Daveは見つかりませんでした"),
    }
}

値の更新と削除

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Alice"), 100);

    // 上書き更新
    scores.insert(String::from("Alice"), 110);
    println!("{:?}", scores.get("Alice")); // Some(110)

    // 削除
    scores.remove("Alice");
    println!("{:?}", scores.get("Alice")); // None
}

キーの存在確認

contains_key() でキーが存在するかどうか確認できます。

if scores.contains_key("Bob") {
    println!("Bobのデータがあります");
}

イテレーション

for ループで全エントリを走査できます。

fn main() {
    let mut scores = HashMap::new();
    scores.insert("Alice", 100);
    scores.insert("Bob", 85);

    for (name, score) in &scores {
        println!("{}: {}", name, score);
    }
}

キーだけ・値だけを取り出したい場合は .keys() / .values() が便利です。

for name in scores.keys() {
    println!("名前: {}", name);
}

for score in scores.values() {
    println!("スコア: {}", score);
}

エントリAPI:Rustならではの便利機能

HashMapの強力な機能のひとつが エントリAPI です。「キーが存在しなければ挿入、存在すれば更新」といった処理を簡潔に書けます。

or_insert:存在しない場合だけ挿入

fn main() {
    let mut scores = HashMap::new();

    // "Alice" が存在しなければ 0 を挿入
    scores.entry(String::from("Alice")).or_insert(0);
    scores.entry(String::from("Alice")).or_insert(999); // 既にあるので変わらない

    println!("{:?}", scores.get("Alice")); // Some(0)
}

カウンターの実装

エントリAPIを使うと、単語の出現回数を数えるカウンターが簡単に作れます。

fn main() {
    let text = "apple banana apple orange banana apple";
    let mut counter = HashMap::new();

    for word in text.split_whitespace() {
        // キーがなければ0を挿入し、その参照を返す
        let count = counter.entry(word).or_insert(0);
        *count += 1; // 参照を通じて値を更新
    }

    println!("{:?}", counter);
    // {"apple": 3, "banana": 2, "orange": 1}
}

or_insert()&mut V を返すため、*count と書くことで値を直接更新できます。


所有権に注意しよう

HashMapにString型の値を挿入すると、所有権がHashMapに移動します。

fn main() {
    let key = String::from("color");
    let val = String::from("blue");

    let mut map = HashMap::new();
    map.insert(key, val);

    // ここで key や val を使おうとするとコンパイルエラー!
    // println!("{}", key); // エラー: value borrowed after move
}

参照渡しが必要な場合は &str を使うか、.clone() で複製してから渡しましょう。


まとめ

操作メソッド
挿入insert(key, value)
取得get(key)Option<&V>
削除remove(key)
存在確認contains_key(key)
条件付き挿入entry(key).or_insert(value)

HashMapはRustの所有権システムとうまく組み合わさった設計になっており、最初は少し戸惑うかもしれません。しかしエントリAPIを使いこなせると、安全かつ簡潔にデータを管理できるようになります。
ぜひ実際にコードを書いて、HashMapに慣れ親しんでみてください!

← 記事一覧に戻る