[Rustのはじめ方] Part13: 配列、ベクタ、ハッシュマップ

Rust

Rustでよく使われる基本的なコレクション型を学びましょう。

多くのプログラミング言語と同様に、Rustにも複数の値をまとめて扱うためのコレクション型があります。ここでは、最も基本的な3つのコレクション型、配列(Array)、ベクタ(Vector)、ハッシュマップ(HashMap)について解説します。

配列 (Array)

配列は、固定長で、同じ型の要素を連続してメモリ上に格納するコレクションです。一度定義すると、そのサイズ(要素数)を変更することはできません。

主に、要素数がコンパイル時に確定している場合や、スタック上にデータを確保したい場合に使用されます。

配列の定義とアクセス

配列は [型; 要素数] という形式で型を定義し、[値1, 値2, ...] という形式で初期化します。

// 型を明示して配列を定義
let numbers: [i32; 5] = [1, 2, 3, 4, 5];

// 型推論を利用して配列を定義
let fruits = ["apple", "banana", "orange"]; // 型は [&str 3] と推論される

// 同じ値で初期化する場合
let zeros = [0; 10]; // 全て0で初期化された要素数10の配列 [0, 0, ..., 0]

要素へのアクセスは、インデックス(0から始まる番号)を使って行います。

let numbers: [i32; 5] = [1, 2, 3, 4, 5];

let first = numbers[0]; // 最初の要素 (1) にアクセス
let third = numbers[2]; // 3番目の要素 (3) にアクセス

println!("最初の要素: {}", first); // -> 最初の要素: 1
println!("3番目の要素: {}", third); // -> 3番目の要素: 3

// 配列の要素数を取得
println!("配列の要素数: {}", numbers.len()); // -> 配列の要素数: 5

// 範囲外のインデックスにアクセスしようとするとパニックが発生します
// let invalid = numbers[10]; // これは実行時エラー(パニック)になります
注意: 配列のインデックスは0から始まります。存在しないインデックスにアクセスしようとすると、プログラムはパニック(強制終了)します。

ベクタ (Vector / Vec<T>)

ベクタは、可変長のリスト構造で、同じ型の要素を格納します。配列とは異なり、実行時に要素を追加したり削除したりできます。データはヒープ領域に確保されます。

要素数が実行時に変わる可能性がある場合や、柔軟に要素を操作したい場合に便利です。

ベクタの定義と操作

ベクタは Vec::new() 関数や vec! マクロを使って生成します。

// 空のベクタを作成 (型を明示する必要がある場合が多い)
let mut numbers: Vec<i32> = Vec::new();

// vec! マクロを使って初期値を持つベクタを作成 (型推論が効く)
let mut fruits = vec!["apple", "banana"]; // 型は Vec<&str> と推論される

要素の追加には push メソッド、削除には pop メソッドなどを使います。

let mut numbers: Vec<i32> = Vec::new();

// 要素を追加 (push)
numbers.push(10);
numbers.push(20);
numbers.push(30);
println!("追加後: {:?}", numbers); // -> 追加後: [10, 20, 30]

// 末尾の要素を削除 (pop) - Option を返す
let last = numbers.pop();
println!("削除された要素: {:?}", last); // -> 削除された要素: Some(30)
println!("削除後: {:?}", numbers);    // -> 削除後: [10, 20]

// インデックスで要素にアクセス (配列と同様)
let first = numbers[0]; // パニックする可能性あり
println!("最初の要素: {}", first); // -> 最初の要素: 10

// get メソッドで安全にアクセス (Option<&T> を返す)
let second = numbers.get(1); // インデックス1の要素への参照をOptionで取得
match second {
    Some(value) => println!("2番目の要素: {}", value), // -> 2番目の要素: 20
    None => println!("2番目の要素はありません"),
}

let out_of_bounds = numbers.get(100); // 範囲外アクセス
match out_of_bounds {
    Some(_) => unreachable!(),
    None => println!("インデックス100には要素がありません"), // -> インデックス100には要素がありません
}

// ベクタの要素数を取得
println!("ベクタの要素数: {}", numbers.len()); // -> ベクタの要素数: 2
ヒント: ベクタの要素にアクセスする際は、パニックを避けるために get メソッドを使うのが安全です。get は要素への参照を Option 型で返します。

ハッシュマップ (HashMap / HashMap<K, V>)

ハッシュマップは、キー(Key)値(Value)のペアを格納するコレクションです。キーを使って非常に高速に値にアクセスできます。要素の順序は保証されません。キーの型と値の型はそれぞれ統一されている必要がありますが、キーと値で異なる型を使うことができます。

データはヒープ領域に確保され、キーに基づいて値が格納される場所が決まります。関連付けられたデータを効率的に検索・管理したい場合に適しています。

ハッシュマップを使用するには、まず標準ライブラリのcollectionsモジュールからHashMapをインポートする必要があります。

use std::collections::HashMap; // HashMap を利用するために use宣言する

ハッシュマップの定義と操作

ハッシュマップは HashMap::new() 関数を使って生成します。

use std::collections::HashMap;

// 空のハッシュマップを作成 (キー: String型, 値: i32型)
let mut scores: HashMap<String, i32> = HashMap::new();

// 要素を追加 (insert)
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

// 既存のキーにinsertすると値が上書きされる
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores); // -> {"Yellow": 50, "Blue": 25} (順序は不定)

// entry と or_insert を使って、キーが存在しない場合のみ挿入
scores.entry(String::from("Red")).or_insert(100);
scores.entry(String::from("Blue")).or_insert(30); // "Blue"は既に存在するので挿入されない
println!("{:?}", scores); // -> {"Red": 100, "Yellow": 50, "Blue": 25}

キーを使って値にアクセスするには get メソッドを使います。

use std::collections::HashMap;

let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

let team_name = String::from("Blue");
// get メソッドで値を取得 (Option<&V> を返す)
let score = scores.get(&team_name); // キーは参照 (&String) を渡す

match score {
    Some(s) => println!("{} チームのスコア: {}", team_name, s), // -> Blue チームのスコア: 10
    None => println!("{} チームのスコアは見つかりません", team_name),
}

// 存在しないキーでアクセス
let unknown_score = scores.get("Green");
match unknown_score {
    Some(_) => unreachable!(),
    None => println!("Green チームのスコアは見つかりません"), // -> Green チームのスコアは見つかりません
}

// ハッシュマップ内の要素をイテレートする
println!("--- 全チームのスコア ---");
for (key, value) in &scores {
    println!("{}: {}", key, value);
}
// 出力例 (順序は不定):
// Yellow: 50
// Blue: 10

まとめ: 配列・ベクタ・ハッシュマップの比較

それぞれのコレクション型の主な特徴を比較してみましょう。

特徴 配列 (Array) ベクタ (Vec<T>) ハッシュマップ (HashMap<K, V>)
サイズ 固定長 (コンパイル時) 可変長 (実行時) 可変長 (実行時)
要素の型 全て同じ型 T 全て同じ型 T キー: 全て同じ型 K
値: 全て同じ型 V
データ格納場所 主にスタック ヒープ ヒープ
要素へのアクセス インデックス ([index]) インデックス ([index]), get(index) キー (get(&key))
順序 保証される 保証される 保証されない
主な用途 要素数が既知の場合、パフォーマンス重視 要素数が変動する場合、リスト操作 キーによる高速な検索、データの関連付け
標準ライブラリ 組み込み std::vec::Vec std::collections::HashMap

これらのコレクション型は、Rustプログラミングにおいて非常に重要です。それぞれの特性を理解し、状況に応じて適切なものを選択できるようにしましょう! 💪

コメント

タイトルとURLをコピーしました