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に慣れ親しんでみてください!