Rustのハッシュマップ入門!基本から実践的な使い方まで解説

2025年4月21日月曜日

Rust

Rustのハッシュマップ、気になっていませんか?

プログラミングを始めたばかりだと、たくさんのデータをどうやって整理したらいいか、迷うこともありますよね。例えば、ユーザー名とその人の得点、商品コードとその在庫数みたいに、「名前」と「情報」をセットで扱いたい場面、結構あると思います。

この記事では、Rustでとっても便利なデータ構造、HashMap(ハッシュマップ)の基本から、実際にコードでどうやって使うのか、気をつけたいポイントまで見ていきます

読み終わるころには、「なるほど、HashMapってこう使うのか!」と、あなたのRustスキルが一段アップしているはずです!

この記事でわかること

  • HashMapって何? どういう時に使うの?
  • RustコードでのHashMapの作り方
  • データの追加、取り出し、削除のやり方
  • HashMapの中身を順番に見ていく方法
  • 使うときに知っておきたいルール(所有権とか!)

Rustのハッシュマップ (HashMap) とは?

さて、HashMapとは一体何者なんでしょう? 

一言でいうと、「名前(キー)」と「値(バリュー)」をペアにして保管しておく箱みたいなものです。ちょうど、本物の辞書で単語(キー)を引くと意味(値)がわかるのと同じ感覚ですね。

大量のデータがあっても、キーさえわかっていれば、目的の値をものすごく速く見つけ出せるのが、HashMapのすごいところ。商品コード(キー)を入れたら、すぐに値段(値)が出てくる自動販売機をイメージすると分かりやすいかもしれません。

Rustでは、標準ライブラリの `std::collections::HashMap` として用意されています。わざわざ自分で作らなくても、すぐに使える便利なやつなんです。

Rustでのハッシュマップの作成方法

じゃあ、実際にRustのコードでHashMapを使ってみましょう! まずは箱、つまりHashMap自体を作る方法から見ていきますね。

use宣言と`HashMap::new()`

HashMapを使うには、まず「これからHashMapを使いますよー」という宣言が必要です。コードの最初にこれを書きます。

use std::collections::HashMap;

次に、空っぽのHashMapを作ってみましょう。`HashMap::new()`を使います。後でデータを追加することがほとんどなので、`mut`をつけて変更可能にしておくのが一般的です。

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new(); // 空のHashMapを作成

    // ここに後でデータを追加していく
    scores.insert(String::from("数学"), 85);
    scores.insert(String::from("英語"), 92);

    println!("作成したHashMap: {:?}", scores); // 中身を確認
    // 出力例: {"英語": 92, "数学": 85} (順序は変わることがあります)
}

上のコードでは、`scores`という名前でHashMapを作っています。まだキーも値も何も入っていない、空の状態ですね。`HashMap::new()`で、まずは空の箱を用意する、と覚えておきましょう。

Rustは賢いので、後で入れるデータから型を推測してくれますが、はっきりさせたい場合は `let mut scores: HashMap<String, i32> = HashMap::new();` のように型を指定することもできますよ。

`collect()`を使った初期化

最初からいくつかデータを入れた状態でHashMapを作りたい時もありますよね。そんな時に便利なのが `collect()` というメソッドです。

例えば、こんな風にキーと値のペアのリスト(ベクタ)を用意しておいて…

use std::collections::HashMap;

fn main() {
    let teams_list = vec![
        (String::from("赤組"), 100),
        (String::from("白組"), 150),
    ];

    // teams_listからHashMapを作成
    // 型注釈 HashMap<_, _> は「キーと値の型は推測してね」という意味
    let teams_map: HashMap<_, _> = teams_list.into_iter().collect();

    println!("Collectで作ったHashMap: {:?}", teams_map);
    // 出力例: {"白組": 150, "赤組": 100} (順序は変わることがあります)
}

上のコードでは、`teams_list` というベクタ(配列みたいなもの)に入っているタプル(ペア)を、`into_iter().collect()` を使って一気にHashMapに変換しています。あらかじめデータがまとまっている場合は `collect()` が楽ちんです。

型指定の部分で `HashMap<_, _>` とアンダースコアを使っているのは、「型はRust君、いい感じに推測してね!」という意味合いです。もちろん `HashMap<String, i32>` と書いてもOKです。

Rustハッシュマップの基本的な使い方 (挿入・取得・削除)

HashMapの箱が作れたら、次はデータの出し入れです! ここでは、データを追加したり、取り出したり、削除したりする基本的な操作を見ていきましょう。

値の挿入・更新 (`insert`)

HashMapに新しいキーと値のペアを追加するには `insert` メソッドを使います。

use std::collections::HashMap;

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

    // データ追加 (キー: 本のタイトル(String), 値: 評価(i32))
    book_reviews.insert(String::from("面白い小説"), 5);
    book_reviews.insert(String::from("ためになる技術書"), 4);

    println!("追加後: {:?}", book_reviews);

    // 同じキーで再度insertすると、値が上書きされる(更新)
    // "面白い小説"の評価を5から4に変更
    book_reviews.insert(String::from("面白い小説"), 4);

    println!("更新後: {:?}", book_reviews);
    // 出力例 (更新後): {"ためになる技術書": 4, "面白い小説": 4} (順序は変わることがあります)
}

`insert` はとってもシンプルですね。キーと値を指定して呼び出すだけです。もし既に同じキーが存在していた場合は、新しい値で上書きされる、という点を覚えておいてください。これが更新の役割も果たします。

値の取得 (`get`)

キーを指定して、それに対応する値を取り出したい時は `get` メソッドを使います。ここで少し注意点があります。

`get` メソッドは、値を直接返すのではなく、`Option` という特別な型で値を包んで返します。なぜかというと、指定したキーが存在しない可能性もあるからです。

  • キーが存在する場合 → `Some(値への参照)` という形で返ってくる
  • キーが存在しない場合 → `None` という形で返ってくる

なので、`get` の結果を使うときは、「ちゃんと値が入っていたか?」を確認する処理が必要です。`match` や `if let` を使うのが安全でおすすめです。

use std::collections::HashMap;

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

    let alice_key = String::from("Alice");
    let charlie_key = String::from("Charlie"); // 存在しないキー

    // Aliceのポイントを取得 (存在するキー)
    // getにはキーの「参照」(&key) を渡すのが基本
    match user_points.get(&alice_key) {
        Some(points) => { // Some(値) で受け取る
            println!("Aliceさんのポイント: {}", points); // 出力: Aliceさんのポイント: 100
        }
        None => { // None ならキーが存在しない
            println!("Aliceさんのデータはありません。");
        }
    }

    // Charlieのポイントを取得 (存在しないキー) - if let を使う場合
    if let Some(points) = user_points.get(&charlie_key) {
        println!("Charlieさんのポイント: {}", points);
    } else {
        println!("Charlieさんのデータはありません。"); // 出力: Charlieさんのデータはありません。
    }
}

このように、`Option` をきちんと処理することで、キーがなくてもプログラムがエラーで止まるのを防げます。

`get`を使うときは `Option` を意識するのがコツです。そして、`get`に渡すキーは、`&` を付けた参照(`&key`)にするのが基本、という点も思い出してください。

値の削除 (`remove`)

HashMapから特定のキーとその値のペアを削除したい場合は `remove` メソッドを使います。これもキーを指定して呼び出します。

use std::collections::HashMap;

fn main() {
    let mut permissions = HashMap::new();
    permissions.insert(String::from("admin"), true);
    permissions.insert(String::from("guest"), false);

    println!("削除前: {:?}", permissions);
    // 出力例: {"guest": false, "admin": true}

    // "guest" キーとその値を削除
    // removeに渡すキーも参照(&key)が基本だが、文字列リテラルの場合は直接書ける
    permissions.remove("guest");

    println!("削除後: {:?}", permissions);
    // 出力例: {"admin": true}

    // 削除されたか確認 (getでNoneが返るはず)
    match permissions.get("guest") {
        Some(_) => println!("guest はまだ存在します。"),
        None => println!("guest は削除されました。"), // 出力: guest は削除されました。
    }
}

`remove` を使うと、指定したキーのデータがごっそり消えます。削除されたかどうかは、後で `get` を使って確認するとわかりますね。`None` が返ってくれば、ちゃんと削除されています。

Rustハッシュマップの反復処理 (ループ)

HashMapに入っているデータを、一つずつ順番に取り出して処理したいこともありますよね。そんなときは `for` ループが使えます。

use std::collections::HashMap;

fn main() {
    let mut fruit_stock = HashMap::new();
    fruit_stock.insert(String::from("りんご"), 10);
    fruit_stock.insert(String::from("みかん"), 25);
    fruit_stock.insert(String::from("バナナ"), 12);

    println!("在庫一覧:");
    // &fruit_stock でループすることで所有権を移動させない
    // ループで得られるキーと値も参照(&)になることが多い
    for (fruit_name, stock_count) in &fruit_stock {
        println!("{} は {} 個あります。", fruit_name, stock_count);
    }
    // 出力例 (順序は実行ごとに変わる可能性あり):
    // 在庫一覧:
    // みかん は 25 個あります。
    // りんご は 10 個あります。
    // バナナ は 12 個あります。
}

このように `for (キー変数, 値変数) in &HashMap変数` という形で書くと、HashMapの中身をぐるっと一周見て回れます。

注意点として、HashMapの中身がどの順番で出てくるかは決まっていません。実行するたびに順番が変わる可能性もあります。もし順番が厳密に決まっていてほしい場合は、HashMap以外のデータ構造(例えば `Vec` や `BTreeMap` など)を検討する必要があるかもしれません。

ループの際に `&fruit_stock` のように `&` を付けているのは、「HashMapそのものを貸してね」という意味です。`&` を付けないと、HashMapの所有権がループの中に移動してしまい、ループの後で `fruit_stock` が使えなくなってしまうので気をつけてください。

Rustのハッシュマップ利用時の注意点 (所有権とキーの型)

HashMapはとても便利ですが、使う上でいくつか知っておくと良いルールがあります。特に「所有権」と「キーにできる型」について見ていきましょう。

これらはRustならではの考え方ですが、一度理解すれば大丈夫です!

所有権について

Rustには「所有権」という大事なルールがあります。HashMapに値を入れるとき、その値の型によって動きが変わります。

  • `String` のように `Copy` ではない型の場合:
    値を `insert` すると、その値の所有権(持ち主である権利)がHashMapに移ります。元の変数は使えなくなります。これはまるで、友達にプレゼントをあげたら、もう自分の物ではなくなるのと同じ感じです。
  • `i32` のような `Copy` できる型の場合:
    値がコピーされてHashMapに入ります。元の変数はそのまま使い続けられます。これは、メモ帳の数字を友達に教えてあげるようなもので、教えてあげても自分のメモは消えませんよね。

特に `String` を値として使うときは、`insert` した後に元の変数が使えなくなる点に注意が必要です。所有権が移動する型か、コピーされる型か、を意識するのがRustプログラミングのコツの一つです。これはキーとして `String` を使う場合も同じルールが適用されます。

キーの型制約 (`Hash` + `Eq`)

HashMapの「キー」には、どんな型でも使えるわけではありません。実は、キーとして使える型には条件があります。

その条件とは、`Hash` と `Eq` という2つの「トレイト」を実装していることです。

  • `Hash`トレイト:キーからハッシュ値という内部的な目印(整理番号みたいなもの)を計算できるようにするため。
  • `Eq`トレイト:キー同士が「まったく同じものか?」を比較できるようにするため。

「トレイトって何?難しそう…」と感じるかもしれませんが、安心してください。

普段よく使う `String` や `i32`, `u64` などの数値型、`bool` 型(true/false)、文字(`char`)などは、最初からこれらのトレイトを持っています。ですから、これらの型をキーにする場合は、特に何も気にする必要はありません。

use std::collections::HashMap;

fn main() {
    // String をキーにする (OK)
    let mut map1 = HashMap::new();
    map1.insert(String::from("apple"), 1);

    // i32 をキーにする (OK)
    let mut map2 = HashMap::new();
    map2.insert(10, "Ten");

    // bool をキーにする (OK)
    let mut map3 = HashMap::new();
    map3.insert(true, "Yes");
    map3.insert(false, "No");

    println!("map1: {:?}", map1); // {"apple": 1}
    println!("map2: {:?}", map2); // {10: "Ten"}
    println!("map3: {:?}", map3); // {false: "No", true: "Yes"} (順不同)
}

もし自分で作った構造体(struct)をキーにしたい場合は、一手間加えて `Hash` と `Eq` を実装(または自動で実装させるおまじないを書く)必要がありますが、入門段階ではまず「よく使う型はだいたいキーにできる」と覚えておけば大丈夫でしょう。

【まとめ】Rustハッシュマップの基礎をマスター

お疲れ様でした! Rustの `HashMap` について、基本的なところから使い方、そしてちょっとした注意点まで見てきました。

今回のポイントをもう一度おさらいしましょう。

  • HashMapはキーと値のペアでデータを効率よく管理する箱
  • `HashMap::new()` で空の箱を、`collect()` で初期値入りの箱を作れる
  • `insert` で追加・更新、`get` で取得(返り値は`Option`!)、`remove` で削除
  • `for` ループで中身を順番(ただし順序は保証されない)に見れる
  • `String`みたいなCopyじゃない型を入れると所有権が移動する点に注意
  • キーには `Hash` と `Eq` が必要(でも普段使う型は心配無用!)

HashMapは、キーを使って素早くデータにアクセスしたい時に本当に役立ちます。データベースのような使い方から、設定情報の管理、一時的なデータの整理まで、活躍の場面はたくさんあります。

最初は所有権や `Option` の扱いで少し戸惑う部分もあるかもしれませんが、実際にコードを書いて、動かして、時にはエラーを出してみて、「なるほど、こう動くのか!」と体験してみるのが一番の近道です。

この記事が、あなたのRust学習の一助となれば嬉しいです。ぜひ、あなたのRustプロジェクトでもHashMapを活用して、もっと便利なプログラムを作ってみてくださいね!

【関連記事】
Rustとは?いま学ぶべき理由と特徴や始め方を詳しく解説

このブログを検索

  • ()

自己紹介

自分の写真
リモートワークでエンジニア兼Webディレクターとして活動しています。プログラミングやAIなど、日々の業務や学びの中で得た知識や気づきをわかりやすく発信し、これからITスキルを身につけたい人にも役立つ情報をお届けします。 note → https://note.com/yurufuri X → https://x.com/mnao111

QooQ