my.code(); Logomy.code();
Rust-8.コレクションと文字列

my.code(); Logomy.code();

  • C++
    • 0.C++の世界へようこそ
    • 1.型システムと制御構造
    • 2.データ集合とモダンな操作
    • 3.ポインタとメモリ管理
    • 4.関数と参照渡し
    • 5.プロジェクトの分割とビルド
    • 6.クラスの基礎
    • 7.クラスを使いこなす
    • 8.継承とポリモーフィズム
    • 9.テンプレート
    • 10.STL ①:コンテナ
    • 11.STL ②:アルゴリズムとラムダ式
    • 12.RAIIとスマートポインタ
  • JavaScript
    • 0.JavaScriptへようこそ
    • 1.基本構文とデータ型
    • 2.制御構文
    • 3.関数とクロージャ
    • 4.'this'の正体
    • 5.オブジェクトとプロトタイプ
    • 6.クラス構文
    • 7.配列とイテレーション
    • 8.非同期処理①: Promise
    • 9.非同期処理②: Async/Await
  • Python
    • 0.環境構築と基本思想
    • 1.基本構文とデータ型
    • 2.リスト、タプル、辞書、セット
    • 3.制御構文と関数
    • 4.モジュールとパッケージ
    • 5.オブジェクト指向プログラミング
    • 6.ファイルの入出力とコンテキストマネージャ
    • 7.例外処理
    • 8.ジェネレータとデコレータ
  • Ruby
    • 0.rubyの世界へようこそ
    • 1.基本構文とデータ型
    • 2.制御構造とメソッド定義
    • 3.すべてがオブジェクト
    • 4.コレクション (Array, Hash, Range)
    • 5.ブロックとイテレータ
    • 6.クラスとオブジェクト
    • 7.モジュールとMix-in
    • 8.Proc, Lambda, クロージャ
    • 9.標準ライブラリの活用
    • 10.テスト文化入門
    • 11.メタプログラミング入門
  • Rust
    • 0.Rustの世界へようこそ
    • 1.基本構文と「不変性」
    • 2.関数と制御フロー
    • 3.所有権
    • 4.借用とスライス
    • 5.構造体とメソッド構文
    • 6.列挙型とパターンマッチ
    • 7.モジュールシステムとパッケージ管理
    • 8.コレクションと文字列
    • 9.エラーハンドリング
    • 10.ジェネリクスとトレイト
    • 11.ライフタイム
  • TypeScript
    • 0.TypeScriptへようこそ
    • 1.基本的な型と型推論
    • 2.オブジェクト、インターフェース、型エイリアス
    • 3.関数の型定義
    • 4.型を組み合わせる
    • 5.ジェネリクス
    • 6.クラスとアクセス修飾子
    • 7.非同期処理とユーティリティ型
my.code(); Logomy.code();

環境構築不要、その場で実践。

ut-code / my-code

Copyright © 2026 ut.code();

ut.code(); について
公式ウェブサイト公式 𝕏 アカウント

第8章: 一般的なコレクションと文字列

これまでの章では、配列やタプルといった固定長のデータ構造を扱ってきました。これらはスタックに格納されるため高速ですが、コンパイル時にサイズが決まっている必要があります。

本章では、Rustの標準ライブラリが提供する、ヒープ領域にデータを格納する動的なコレクションについて学びます。これらは実行時にサイズを変更可能です。特に、他の言語経験者が躓きやすい「Rustにおける文字列(UTF-8)の扱い」には重点を置いて解説します。

主に以下の3つを扱います。

  1. ベクタ (Vec<T>): 可変長のリスト。
  2. 文字列 (String): UTF-8エンコードされたテキスト。
  3. ハッシュマップ (HashMap<K, V>): キーと値のペア。

ベクタ (Vec<T>):可変長配列

ベクタは、同じ型の値をメモリ上に連続して配置するデータ構造です。C++の std::vector や Javaの ArrayList、Pythonのリストに近いものです。

ベクタの作成と更新

Vec::new() 関数または vec! マクロを使用して作成します。要素を追加するには push メソッドを使いますが、ベクタを変更するためには mut で可変にする必要があります。

ファイルを編集:vector_basics.rs
fn main() {
    // 空のベクタを作成(型注釈が必要な場合がある)
    let mut v: Vec<i32> = Vec::new();
    v.push(5);
    v.push(6);
    v.push(7);

    // vec!マクロを使うと型推論が効くため記述が楽
    let mut v2 = vec![1, 2, 3];
    v2.push(4);

    println!("v: {:?}", v);
    println!("v2: {:?}", v2);
    
    // popで末尾の要素を削除して取得(Optionを返す)
    let last = v2.pop();
    println!("Popped: {:?}", last);
    println!("v2 after pop: {:?}", v2);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のvector_basics.rsに書かれている内容を実行します。
v: [5, 6, 7]
v2: [1, 2, 3, 4]
Popped: Some(4)
v2 after pop: [1, 2, 3]

要素へのアクセス

要素へのアクセスには「インデックス記法 []」と「get メソッド」の2通りの方法があります。安全性において大きな違いがあります。

  • &v[i]: 存在しないインデックスにアクセスするとパニックを起こします。
  • v.get(i): Option<&T> を返します。範囲外の場合は None になるため、安全に処理できます。
ファイルを編集:vector_access.rs
fn main() {
    let v = vec![10, 20, 30, 40, 50];

    // 方法1: インデックス(確実に存在するとわかっている場合に使用)
    let third: &i32 = &v[2];
    println!("3番目の要素は {}", third);

    // 方法2: getメソッド(範囲外の可能性がある場合に使用)
    match v.get(100) {
        Some(third) => println!("101番目の要素は {}", third),
        None => println!("101番目の要素はありません"),
    }

    // イテレーション
    // &v とすることで所有権を移動させずに参照でループする
    print!("要素: ");
    for i in &v {
        print!("{} ", i);
    }
    println!();
    
    // 値を変更しながらイテレーション
    let mut v_mut = vec![1, 2, 3];
    for i in &mut v_mut {
        *i += 50; // 参照外し演算子(*)を使って値を書き換える
    }
    println!("変更後: {:?}", v_mut);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のvector_access.rsに書かれている内容を実行します。
3番目の要素は 30
101番目の要素はありません
要素: 10 20 30 40 50 
変更後: [51, 52, 53]

文字列 (String) と UTF-8

Rustにおける文字列は、他の言語経験者にとって最も混乱しやすい部分の一つです。 Rustの文字列は、UTF-8エンコードされたバイトのコレクションとして実装されています。

String と &str の違い(復習)

  • String: 所有権を持つ、伸長可能な、ヒープ上の文字列(Vec<u8> のラッパー)。
  • &str (文字列スライス): どこか(バイナリ領域やヒープ領域)にある文字列データへの参照。

文字列の操作

String は Vec と同様に push_str や + 演算子で結合できます。

ファイルを編集:string_ops.rs
fn main() {
    let mut s = String::from("foo");
    s.push_str("bar"); // 文字列スライスを追加
    s.push('!');       // 1文字追加
    println!("{}", s);

    let s1 = String::from("Hello, ");
    let s2 = String::from("World!");
    
    // + 演算子を使用。
    // s1はムーブされ、以降使用できなくなることに注意
    // シグネチャは fn add(self, s: &str) -> String に近いため
    let s3 = s1 + &s2; 
    
    println!("{}", s3);
    // println!("{}", s1); // コンパイルエラー:s1はムーブ済み
    
    // format!マクロを使うと所有権を奪わず、読みやすく結合できる
    let s4 = String::from("tic");
    let s5 = String::from("tac");
    let s6 = String::from("toe");
    
    let s_all = format!("{}-{}-{}", s4, s5, s6);
    println!("{}", s_all);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のstring_ops.rsに書かれている内容を実行します。
foobar!
Hello, World!
tic-tac-toe

なぜインデックスアクセスができないのか?

多くの言語では s[0] で1文字目を取得できますが、Rustではコンパイルエラーになります。

Rustの文字列はUTF-8です。ASCII文字は1バイトですが、日本語のような文字は3バイト(またはそれ以上)を使用します。

  • "A" -> [0x41] (1バイト)
  • "あ" -> [0xE3, 0x81, 0x82] (3バイト)

もし "あ" という文字列に対して s[0] で1バイト目を取得できたとしても、それは 0xE3 という意味のないバイト値であり、プログラマが期待する「あ」ではありません。Rustはこの誤解を防ぐために、インデックスアクセスを禁止しています。

文字列の中身を見るには、「バイトとして見る」か「文字(スカラ値)として見る」かを明示する必要があります。

ファイルを編集:string_utf8.rs
fn main() {
    let s = "こんにちは"; // UTF-8で各文字3バイト

    // NG: s[0] はコンパイルエラー

    // 文字(char)として反復処理
    // RustのcharはUnicodeスカラ値(4バイト)
    print!("Chars: ");
    for c in s.chars() {
        print!("{} ", c);
    }
    println!();

    // バイトとして反復処理
    print!("Bytes: ");
    for b in s.bytes() {
        print!("{:x} ", b); // 16進数で表示
    }
    println!();
    
    // 部分文字列(スライス)の取得には範囲指定が必要
    // ただし、文字の境界に合わないバイトを指定すると実行時にパニックする
    let s_slice = &s[0..3]; // 最初の3バイト=「こ」
    println!("Slice: {}", s_slice);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のstring_utf8.rsに書かれている内容を実行します。
Chars: こ ん に ち は 
Bytes: e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 8a 
Slice: こ

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

ハッシュマップは、キーと値をマッピングしてデータを格納します。Pythonの dict、JavaScriptの Map やオブジェクト、Rubyの Hash に相当します。標準ライブラリの std::collections モジュールからインポートする必要があります。

基本的な操作

ファイルを編集:hashmap_demo.rs
use std::collections::HashMap;

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

    // 挿入
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    // 値の取得(getはOption<&V>を返す)
    let team_name = String::from("Blue");
    if let Some(score) = scores.get(&team_name) {
        println!("{}: {}", team_name, score);
    }

    // 反復処理(順序は保証されない)
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のhashmap_demo.rsに書かれている内容を実行します。
Blue: 10
Yellow: 50
Blue: 10

所有権の移動

HashMap にキーや値を挿入すると、String のような所有権を持つ型はマップ内にムーブされます(i32 のような Copy トレイトを持つ型はコピーされます)。挿入後に元の変数を使おうとするとエラーになります。

エントリ API による更新

「キーが存在しなければ値を挿入し、存在すれば何もしない(あるいは値を更新する)」というパターンは非常に一般的です。Rustでは entry APIを使うとこれを簡潔に書けます。

ファイルを編集:hashmap_update.rs
use std::collections::HashMap;

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

    // 上書き(同じキーでinsertすると値は上書きされる)
    scores.insert(String::from("Blue"), 25);
    println!("Blue updated: {:?}", scores);

    // キーがない場合のみ挿入 (or_insert)
    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50); // 既に25があるので無視される
    println!("Entry check: {:?}", scores);

    // 既存の値に基づいて更新(単語の出現回数カウントなど)
    let text = "hello world wonderful world";
    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        // or_insertは挿入された値への可変参照(&mut V)を返す
        let count = map.entry(word).or_insert(0);
        *count += 1; // 参照外ししてインクリメント
    }

    println!("Word count: {:?}", map);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のhashmap_update.rsに書かれている内容を実行します。
Blue updated: {"Blue": 25}
Entry check: {"Blue": 25, "Yellow": 50}
Word count: {"world": 2, "hello": 1, "wonderful": 1}

この章のまとめ

  • Vec<T>: 同じ型の要素を可変長で保持します。範囲外アクセスには注意し、必要なら get メソッドを使用します。
  • String: UTF-8エンコードされたバイト列のラッパーです。インデックス [i] によるアクセスは禁止されており、文字として扱うには .chars() を、バイトとして扱うには .bytes() を使用します。
  • HashMap<K, V>: キーバリューストアです。entry APIを使用すると、「存在確認してから挿入・更新」という処理を効率的かつ安全に記述できます。

練習問題1:整数のリスト分析

整数のベクタ vec![1, 10, 5, 2, 10, 5, 20, 5] が与えられたとき、以下の3つを計算して表示するプログラムを作成してください。

  1. 平均値 (Mean)
  2. 中央値 (Median): リストをソートしたときに真ん中に来る値。
  3. 最頻値 (Mode): 最も頻繁に出現する値(ヒント:ハッシュマップを使って出現回数を数えます)。
ファイルを編集:practice9_1.rs
fn main() {
    let numbers = vec![1, 10, 5, 2, 10, 5, 20, 5];


}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のpractice9_1.rsに書かれている内容を実行します。
平均値: 7.25
中央値: 5
最頻値: 5

問題2:ピッグ・ラテン (Pig Latin) 変換

文字列を「ピッグ・ラテン」と呼ばれる言葉遊びに変換する関数を作成してください。ルールは以下の通りです。

  1. 単語が母音 (a, i, u, e, o) で始まる場合、単語のお尻に -hay を追加します。
    • 例: apple -> apple-hay
  2. 単語が子音で始まる場合、最初の文字を単語のお尻に移動し、-ay を追加します。
    • 例: first -> irst-fay

アルファベットのみ、小文字のみの想定で構いません。

ファイルを編集:practice9_2.rs
fn pig_latin(word: &str) -> String {
    // ここに変換ロジックを実装
    

}
fn main() {
    println!("{}", pig_latin("apple"));
    println!("{}", pig_latin("first"));
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のpractice9_2.rsに書かれている内容を実行します。
apple-hay
irst-fay
前のページ« モジュールシステムとパッケージ管理
次のページエラーハンドリング »