Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rustをゼロから学ぶ

プログラミング未経験者がRustを習得し、生成AIを活用してWebアプリを構築できるようになるための教材へようこそ。

対象者

  • シェル操作(ディレクトリ移動など)ができる方
  • プログラミングは未経験の方
  • コンパイルの概念を知らない方

学習の流れ

Phase 0: 基盤構築(コンピュータとプログラミングの基礎)
    ↓
Phase 1: Rust基礎(変数、制御フロー、関数)
    ↓
Phase 2: 所有権とメモリ(Rust最重要概念)
    ↓
Phase 3: 構造化プログラミング(struct、enum、trait)
    ↓
Phase 4: エコシステムと実践(Cargo、非同期)
    ↓
Phase 5: Webアプリ開発(Axum、データベース)
    ↓
最終プロジェクト: ブックマーク管理Webアプリ

学習の進め方

  1. 各Phaseのドキュメントを順番に読む
  2. 各ドキュメント末尾の**確認テスト(5問)**で理解度をチェック
  3. 実践プロジェクトでコードを書いて動かす
  4. わからないことは生成AIに質問(活用ガイドライン参照)

確認テストについて

各ドキュメントの末尾に5問の確認テストがあります:

問題タイプ問数内容
概念理解(選択式)2問基本概念の定着確認
コード読解1問コードの動作を予測
エラー修正1問コンパイルエラーを修正
実践課題1問小さなコードを書く

進捗管理機能: 各テストの「理解できた」チェックボックスをクリックすると、進捗がブラウザに保存されます。進捗ダッシュボードで全体の学習状況を確認できます。

各Phaseの概要

Phase 0: 基盤構築

コンピュータの仕組み、プログラミングの概念、Rustのセットアップ、Hello Worldまで。

Phase 1: Rust基礎

変数と型、演算子、制御フロー、関数、コレクション、エラー処理の基本。

Phase 2: 所有権とメモリ(最重要)

Rust独自の概念。スタックとヒープ、所有権、参照と借用、ライフタイム。

Phase 3: 構造化プログラミング

構造体、列挙型、トレイト、ジェネリクス、モジュール、高度なエラー処理。

Phase 4: エコシステムと実践

Cargoの応用、外部クレートの選定、テスト、ドキュメント、非同期プログラミング。

Phase 5: Webアプリ開発

HTTP/REST基礎、Axumフレームワーク、データベース連携、認証、デプロイ。

実践プロジェクト

プロジェクトPhase学習ポイント
p0_hello_rust0Rustセットアップ、コンパイル
p1_guessing_game1入力、乱数、ループ
p2_calculator1関数、match、エラー処理
p3_text_processor2String、&str、所有権
p4_task_cli3struct、enum、JSON、モジュール
p5_weather_cli4非同期、API呼び出し、テスト
p6_bookmark_api5Axum、SQLx、認証
final_bookmark_app全てフルスタックWebアプリ

困ったときは

さあ、始めましょう!

最初のドキュメントは:

Phase 0-1: コンピュータの基礎

進捗ダッシュボード

学習の進捗状況を確認できます。クイズの結果はブラウザに自動保存されます。

全体の進捗

0%

クイズ完了: 0 / 34

Phase別進捗

🏗️ Phase 0: 基盤構築

0%

0 / 5 クイズ

    📚 Phase 1: Rust基礎

    0%

    0 / 6 クイズ

      🔑 Phase 2: 所有権

      0%

      0 / 5 クイズ

        🧱 Phase 3: 構造化

        0%

        0 / 6 クイズ

          🛠️ Phase 4: エコシステム

          0%

          0 / 5 クイズ

            🌐 Phase 5: Web開発

            0%

            0 / 7 クイズ

              学習のヒント

              • 各ドキュメントの末尾にある確認テスト(5問)に取り組みましょう
              • 全問正解すると🎉マークが表示されます
              • 進捗データは「エクスポート」でJSON形式でバックアップできます
              • 別のブラウザに移行する場合は「インポート」で復元できます

              コンピュータの基本構造

              プログラミングを始める前に、コンピュータがどのように動いているかを理解しましょう。

              コンピュータの3つの主要部品

              コンピュータは大きく分けて3つの部品で構成されています。

              ┌─────────────────────────────────────────────────┐
              │                コンピュータ                      │
              │                                                 │
              │  ┌─────────┐  ┌─────────┐  ┌─────────────────┐ │
              │  │  CPU    │  │ メモリ   │  │ ストレージ      │ │
              │  │ (頭脳)  │  │ (作業台) │  │ (倉庫)         │ │
              │  └─────────┘  └─────────┘  └─────────────────┘ │
              │       ↑            ↑              ↑            │
              │       └────────────┴──────────────┘            │
              │              データのやり取り                    │
              └─────────────────────────────────────────────────┘
              

              1. CPU(Central Processing Unit)- 頭脳

              役割: 計算と命令の実行

              CPUは「中央演算処理装置」と呼ばれ、コンピュータの頭脳です。

              • プログラムの命令を1つずつ読み取って実行する
              • 足し算、引き算などの計算を行う
              • 条件に応じて次に何をするか判断する

              例え: 料理人

              料理人が「レシピ(プログラム)」を読んで、「材料を切る」「火にかける」などの指示を1つずつ実行するイメージです。

              2. メモリ(RAM)- 作業台

              役割: 一時的なデータの保管

              メモリは「作業台」のようなものです。

              • CPUが今まさに使っているデータを置く場所
              • 電源を切ると中身は消える(揮発性)
              • 高速にアクセスできる

              例え: 料理の作業台

              料理人が今使っている材料や調理中の料理を置く作業台です。作業が終わったら片付けます。

              3. ストレージ(SSD/HDD)- 倉庫

              役割: 永続的なデータの保管

              ストレージは「倉庫」のようなものです。

              • ファイルやプログラムを保存する場所
              • 電源を切っても中身は消えない(不揮発性)
              • メモリより遅いがたくさん保存できる

              例え: 食材倉庫

              使わない材料や保存食を置いておく倉庫です。必要なときに取り出します。

              実際の動作の流れ

              あなたがプログラムを実行するとき、こんな流れで動きます:

              1. ストレージからプログラムを読み込む
                 [ストレージ] ──読み込み──→ [メモリ]
              
              2. CPUがメモリ上のプログラムを実行
                 [メモリ] ←──命令取得──→ [CPU]
                         ←──結果保存──→
              
              3. 結果をストレージに保存(必要な場合)
                 [メモリ] ──書き込み──→ [ストレージ]
              

              なぜこれを知る必要があるのか?

              Rustを学ぶ上で、特にメモリの理解が重要です。

              Rustは「メモリ安全」を重視する言語で、プログラムがメモリをどう使うかを細かく制御します。Phase 2で「所有権」という概念を学びますが、それはメモリの管理に関係しています。

              今は「メモリは一時的な作業台」ということを覚えておいてください。

              まとめ

              部品役割特徴
              CPU計算・実行頭脳、命令を処理
              メモリ一時保管高速、電源OFFで消える
              ストレージ永続保管大容量、電源OFFでも残る

              確認テスト

              Q1. CPUの役割として正しいものはどれ?

              Q2. メモリ(RAM)の特徴として正しいものはどれ?

              Q3. プログラム実行の正しい順序はどれ?

              Q4. 「メモリはファイルを永続的に保存する場所」という説明について正しいものはどれ?

              Q5. ストレージの特徴として正しいものはどれ?


              次のドキュメント: 02_what_is_programming.md

              プログラミングとは何か

              プログラムとは「コンピュータへの指示書」

              プログラムとは、コンピュータに「何をしてほしいか」を伝える指示書です。

              人間が書く指示書        コンピュータが理解
              (ソースコード)    →   (機械語)    →    実行
              

              人間は日本語や英語で考えますが、コンピュータは「0」と「1」の組み合わせ(機械語)しか理解できません。

              プログラミング言語は、人間が書きやすい形式で指示を書き、それをコンピュータが理解できる形に変換するための仕組みです。

              料理のレシピに例えると

              プログラムは料理のレシピに似ています。

              カレーのレシピ(人間向け):

              1. 玉ねぎを切る
              2. 肉を炒める
              3. 野菜を加える
              4. 水を入れて煮込む
              5. ルーを入れる
              6. 完成!
              

              プログラム(コンピュータ向け):

              fn main() {
                  println!("玉ねぎを切る");
                  println!("肉を炒める");
                  println!("野菜を加える");
                  println!("水を入れて煮込む");
                  println!("ルーを入れる");
                  println!("完成!");
              }

              どちらも「手順を順番に書いたもの」という点で同じです。

              ソースコードとは

              プログラマーが書くプログラムの文章をソースコードと呼びます。

              • 人間が読み書きできるテキスト形式
              • 特定のプログラミング言語のルールに従って書く
              • ファイルとして保存する(Rustの場合は .rs という拡張子)

              Rustのソースコード例:

              // hello.rs(ファイル名)
              fn main() {
                  println!("Hello, World!");
              }

              プログラミングの基本的な流れ

              ┌──────────────┐
              │ 1. 書く      │  ソースコードを書く(hello.rs)
              └──────┬───────┘
                     ↓
              ┌──────────────┐
              │ 2. 変換      │  コンピュータが理解できる形に変換
              └──────┬───────┘
                     ↓
              ┌──────────────┐
              │ 3. 実行      │  プログラムを動かす
              └──────┬───────┘
                     ↓
              ┌──────────────┐
              │ 4. 結果確認   │  期待通りに動いたか確認
              └──────────────┘
              

              この「変換」の部分が、次のドキュメントで説明する「コンパイル」です。

              プログラミング言語の種類

              世の中には多くのプログラミング言語があります。

              言語特徴主な用途
              Python読みやすく書きやすいAI、データ分析
              JavaScriptブラウザで動くWebサイト
              Java大規模システム向け企業システム
              Rust安全で高速システム、Web、組み込み

              どの言語も「コンピュータへの指示を書く」という目的は同じですが、書き方や得意分野が異なります。

              なぜRustを学ぶのか

              Rustには以下の特徴があります:

              1. 安全性: メモリに関するバグを防ぐ仕組みがある
              2. 高速性: C/C++と同等の実行速度
              3. 現代的: 最近の言語設計のベストプラクティスを取り入れている
              4. 成長中: 多くの企業が採用し、需要が増えている

              特に「安全性」の部分は、Rust独自の「所有権」という仕組みで実現されています(Phase 2で詳しく学びます)。

              エラーは味方

              プログラミングを始めると、たくさんのエラーに遭遇します。

              エラーは失敗ではありません。学習の機会です。

              Rustは特に親切なエラーメッセージを出してくれる言語です。エラーメッセージを読む習慣をつけましょう。

              error[E0384]: cannot assign twice to immutable variable `x`
               --> src/main.rs:3:5
                |
              2 |     let x = 5;
                |         -
                |         |
                |         first assignment to `x`
                |         help: consider making this binding mutable: `mut x`
              3 |     x = 6;
                |     ^^^^^ cannot assign twice to immutable variable
              

              このエラーは「xという変数に2回値を代入しようとしているけど、変更不可(immutable)だよ。mutをつけると変更可能になるよ」と教えてくれています。

              まとめ

              • プログラムはコンピュータへの指示書
              • ソースコードは人間が書くプログラムの文章
              • エラーは学習の機会、恐れずに読もう
              • Rustは安全で高速な現代的言語

              確認テスト

              Q1. ソースコードとは何か?

              Q2. プログラミングの基本的な流れとして正しい順番は?

              Q3. 以下のRustコードは何を出力する?
              fn main() { println!("こんにちは"); println!("Rust"); }

              Q4. 以下のエラーメッセージは何を伝えている?
              error: expected `;`, found `}`

              Q5. プログラミングの本質として最も適切な説明は?


              次のドキュメント: 03_compile_vs_interpret.md

              コンパイルとインタプリタ

              プログラミング言語には、ソースコードを実行する方法が2種類あります。

              2つの実行方式

              コンパイル方式(Rustはこちら)

              ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
              │ ソースコード │ ──→ │ コンパイラ   │ ──→ │ 実行ファイル │
              │ (hello.rs)  │     │ (rustc)    │     │ (hello)     │
              └─────────────┘     └─────────────┘     └─────────────┘
                                                             │
                                                             ↓
                                                      ┌─────────────┐
                                                      │ 何度でも実行 │
                                                      └─────────────┘
              

              特徴:

              • 実行前にソースコード全体を機械語に変換(コンパイル)
              • 一度変換すると、何度でも高速に実行できる
              • 変換時にエラーを検出できる

              例え: 本の翻訳

              英語の本を日本語に翻訳して出版するようなもの。翻訳には時間がかかるが、一度翻訳すれば何度でも読める。

              インタプリタ方式

              ┌─────────────┐     ┌─────────────┐
              │ ソースコード │ ──→ │インタプリタ  │ ──→ 実行
              │ (hello.py)  │     │ (python)    │
              └─────────────┘     └─────────────┘
                                        │
                                        ↓
                                  1行ずつ変換しながら実行
              

              特徴:

              • 実行しながら1行ずつ変換(逐次解釈)
              • すぐに実行できる
              • 実行速度はコンパイル方式より遅い

              例え: 同時通訳

              英語のスピーチを同時通訳するようなもの。すぐに理解できるが、通訳しながらなので時間がかかる。

              比較表

              観点コンパイル方式インタプリタ方式
              変換タイミング実行前に全体を変換実行中に1行ずつ
              実行速度高速比較的遅い
              エラー検出実行前に検出実行時に検出
              開発サイクル変換に時間がかかるすぐ試せる
              代表的な言語Rust, C, C++, GoPython, JavaScript, Ruby

              Rustがコンパイル言語である意味

              Rustはコンパイル方式を採用しています。これには大きなメリットがあります。

              1. 実行前にエラーを発見できる

              fn main() {
                  let x = 5;
                  x = 6;  // エラー!変更不可の変数を変更しようとしている
              }

              このコードはコンパイル時にエラーになります。プログラムを動かす前に問題がわかります。

              インタプリタ方式だと、実際にこの行が実行されるまでエラーがわかりません。

              2. 高速に実行できる

              コンパイル済みのプログラムは機械語になっているので、CPUが直接実行できます。

              実行速度のイメージ:
              Rust (コンパイル): ████████████████████ 100%
              Python (インタプリタ): ███ 10-30%
              

              3. 配布が簡単

              コンパイルして作られた実行ファイルは、そのまま他の人に渡せます。受け取った人はRustをインストールしなくても実行できます。

              コンパイルの流れ(Rust)

              ┌─────────────┐
              │ hello.rs    │  ソースコードを書く
              └──────┬──────┘
                     │ rustc hello.rs(または cargo build)
                     ↓
              ┌─────────────┐
              │ コンパイル   │
              │ ・文法チェック│
              │ ・型チェック │
              │ ・所有権チェック│
              │ ・最適化    │
              └──────┬──────┘
                     │
                     ↓
              ┌─────────────┐
              │ hello       │  実行ファイル(機械語)
              └──────┬──────┘
                     │ ./hello
                     ↓
              ┌─────────────┐
              │ 実行結果    │  Hello, World!
              └─────────────┘
              

              Rustのコンパイラは特にチェックが厳しいことで知られています。

              • 文法チェック:書き方が正しいか
              • 型チェック:データの種類が合っているか
              • 所有権チェック:メモリの使い方が安全か

              これらのチェックのおかげで、実行時のバグを減らせます。

              コンパイルエラーは友達

              コンパイル時にエラーが出ると、最初は戸惑うかもしれません。

              しかし、実行時にエラーが出るより、コンパイル時にエラーが出る方がはるかに良いのです。

              なぜなら:

              • 実行時エラー:本番環境でユーザーに影響する可能性
              • コンパイルエラー:開発中に気づける、ユーザーには影響しない

              Rustのコンパイラはエラーメッセージが親切です。エラーを恐れず、メッセージをよく読みましょう。

              まとめ

              • コンパイル方式: 実行前に全体を変換、高速、エラーを早期発見
              • インタプリタ方式: 1行ずつ変換しながら実行、すぐ試せる
              • Rustはコンパイル方式: 安全性と速度を両立
              • コンパイルエラーは味方: 実行前に問題を発見できる

              確認テスト

              Q1. コンパイル方式の特徴として正しいものはどれ?

              Q2. Rustがコンパイル言語であるメリットとして正しいものはどれ?

              Q3. rustc hello.rs を実行すると何が起こる?

              Q4. 「インタプリタ方式は実行前に全体をチェックするのでエラーを早期に発見できる」この説明は正しい?

              Q5. 大量のデータを高速に処理するプログラムに適した方式は?


              次のドキュメント: 04_rust_setup.md

              Rust開発環境の構築

              Rustでプログラミングを始めるための環境を整えましょう。

              必要なもの

              1. Rust本体(rustup経由でインストール)
              2. テキストエディタ(VS Codeを推奨)

              Rustのインストール

              macOS / Linux の場合

              ターミナルを開いて、以下のコマンドを実行します:

              curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
              

              インストール中に選択肢が表示されたら、1(default)を選んでEnterを押します。

              インストール完了後、ターミナルを再起動するか、以下を実行:

              source $HOME/.cargo/env
              

              Windows の場合

              1. https://rustup.rs にアクセス
              2. 「RUSTUP-INIT.EXE」をダウンロードして実行
              3. 画面の指示に従ってインストール

              インストールの確認

              ターミナル(またはコマンドプロンプト)で以下を実行:

              rustc --version
              

              バージョンが表示されれば成功です:

              rustc 1.XX.X (xxxxxxxx 2024-XX-XX)
              

              インストールされるツール

              rustupをインストールすると、以下のツールが使えるようになります:

              ツール役割
              rustupRustのバージョン管理
              rustcRustコンパイラ
              cargoパッケージマネージャ・ビルドツール

              rustup

              Rust本体のバージョンを管理するツールです。

              # Rustを最新版に更新
              rustup update
              
              # インストールされているバージョンを確認
              rustup show
              

              rustc

              Rustコンパイラです。ソースコードを実行ファイルに変換します。

              # 単一ファイルをコンパイル
              rustc hello.rs
              

              cargo(重要)

              Rustの「パッケージマネージャ」兼「ビルドツール」です。実際の開発ではcargoを使います

              # 新しいプロジェクトを作成
              cargo new my_project
              
              # プロジェクトをビルド
              cargo build
              
              # プロジェクトを実行
              cargo run
              
              # コードをチェック(コンパイルはしない)
              cargo check
              

              エディタの設定(VS Code)

              VS Codeのインストール

              1. https://code.visualstudio.com からダウンロード
              2. インストーラを実行

              Rust拡張機能のインストール

              VS Codeを開いて:

              1. 左のサイドバーで拡張機能アイコン(四角が4つ)をクリック
              2. 「rust-analyzer」を検索
              3. 「Install」をクリック

              rust-analyzerは以下の機能を提供します:

              • コード補完
              • エラーのリアルタイム表示
              • 型情報の表示
              • コードジャンプ

              動作確認

              環境が正しく設定されたか確認しましょう。

              1. プロジェクトを作成

              cargo new hello_rust
              cd hello_rust
              

              2. 生成されたファイルを確認

              hello_rust/
              ├── Cargo.toml    # プロジェクトの設定ファイル
              └── src/
                  └── main.rs   # ソースコード
              

              Cargo.tomlの中身:

              [package]
              name = "hello_rust"
              version = "0.1.0"
              edition = "2021"
              
              [dependencies]
              

              src/main.rsの中身:

              fn main() {
                  println!("Hello, world!");
              }

              3. 実行

              cargo run
              

              以下が表示されれば成功です:

                 Compiling hello_rust v0.1.0 (/path/to/hello_rust)
                  Finished dev [unoptimized + debuginfo] target(s) in 0.XXs
                   Running `target/debug/hello_rust`
              Hello, world!
              

              トラブルシューティング

              「command not found: cargo」と表示される

              ターミナルを再起動するか、以下を実行:

              source $HOME/.cargo/env
              

              それでも動かない場合は、~/.cargo/binがPATHに含まれているか確認:

              echo $PATH | grep cargo
              

              VS Codeでrust-analyzerが動かない

              1. VS Codeを再起動
              2. cargo build を一度実行してから確認
              3. rust-analyzer拡張機能を再インストール

              コンパイルが非常に遅い

              初回のコンパイルは依存関係のダウンロードとビルドがあるため遅いです。2回目以降は速くなります。

              まとめ

              • rustup: Rust本体のインストール・更新
              • cargo: プロジェクト管理・ビルド(これを主に使う)
              • VS Code + rust-analyzer: 快適な開発環境

              環境構築は最初だけです。一度設定すれば、あとはコードを書くことに集中できます。


              確認テスト

              Q1. Rustプロジェクトの作成・ビルド・実行に主に使うツールはどれ?

              Q2. cargo new my_project を実行すると何が作成される?

              Q3. cargo new hellocd hellocargo run を実行すると何が表示される?

              Q4. error: could not find Cargo.toml というエラーの原因は?

              Q5. Rustのバージョンを最新に更新するコマンドは?


              次のドキュメント: 05_first_program.md

              はじめてのRustプログラム

              実際にRustのプログラムを書いて動かしてみましょう。

              Hello, World!

              プログラミング学習の伝統として、最初に書くプログラムは「Hello, World!」を表示するものです。

              プロジェクトの作成

              cargo new hello_world
              cd hello_world
              

              コードを見てみよう

              src/main.rs を開くと、以下のコードが書かれています:

              fn main() {
                  println!("Hello, world!");
              }

              たった3行!これがRustプログラムの最小構成です。

              1行ずつ解説

              fn main() {
              • fn: 「function(関数)」の略。関数を定義するキーワード
              • main: 関数の名前。mainはプログラムの開始地点
              • (): 引数リスト。今は空っぽ
              • {: 関数の中身の始まり
              #![allow(unused)]
              fn main() {
                  println!("Hello, world!");
              }
              • println!: 画面に文字を出力するマクロ(!がついているのがマクロの印)
              • "Hello, world!": 表示する文字列
              • ;: 文の終わり
              #![allow(unused)]
              fn main() {
              }
              }
              • }: 関数の中身の終わり

              実行

              cargo run
              

              出力:

              Hello, world!
              

              コードを変更してみよう

              表示する文字を変える

              src/main.rsを以下のように変更:

              fn main() {
                  println!("こんにちは、Rust!");
              }

              保存して実行:

              cargo run
              

              出力:

              こんにちは、Rust!
              

              複数行を表示する

              fn main() {
                  println!("1行目");
                  println!("2行目");
                  println!("3行目");
              }

              出力:

              1行目
              2行目
              3行目
              

              わざとエラーを起こしてみよう

              エラーメッセージを読む練習をしましょう。

              エラー1: セミコロン忘れ

              fn main() {
                  println!("Hello, world!")  // セミコロンがない!
              }
              cargo run
              

              エラー:

              error: expected `;`, found `}`
               --> src/main.rs:3:1
                |
              2 |     println!("Hello, world!")
                |                              - help: add `;` here
              3 | }
                | ^ unexpected token
              

              読み方:

              • expected ;, found }``:セミコロンを期待したのに}が見つかった
              • help: add ; here:ここにセミコロンを追加して

              エラー2: 閉じカッコ忘れ

              fn main() {
                  println!("Hello, world!";  // 閉じカッコがない!
              }

              エラー:

              error: expected `)`, found `;`
               --> src/main.rs:2:29
                |
              2 |     println!("Hello, world!";
                |                             ^ expected `)`
              

              エラー3: クォート忘れ

              fn main() {
                  println!(Hello, world!);  // クォートがない!
              }

              エラー:

              error: expected `(`, found `Hello`
               --> src/main.rs:2:14
                |
              2 |     println!(Hello, world!);
                |              ^^^^^ expected `(`
              

              プログラムの構造を理解する

              fn main() {           // ← プログラムの入口(エントリーポイント)
                  // ここに処理を書く
                  println!("処理1");
                  println!("処理2");
              }                     // ← プログラムの終わり

              Rustプログラムは必ずmain関数から実行が始まります。

              コメントの書き方

              fn main() {
                  // これは1行コメント
                  println!("Hello!");  // 行末コメント
              
                  /*
                  これは
                  複数行
                  コメント
                  */
              }

              コメントはプログラムの実行に影響しません。メモとして使います。

              cargo runとcargo build

              cargo run

              コンパイル + 実行を一度に行います。

              cargo run
              

              cargo build

              コンパイルのみ行います。実行ファイルはtarget/debug/に作られます。

              cargo build
              ./target/debug/hello_world
              

              cargo check

              コンパイルエラーがないかチェックします。実行ファイルは作りません。

              cargo check
              

              使い分け:

              • 開発中は cargo check(速い)
              • 動作確認は cargo run
              • 配布用は cargo build --release(最適化される)

              実践プロジェクト

              projects/p0_hello_rust/ に移動して、以下を試してみましょう。

              1. 自分の名前を表示するプログラムを書く
              2. 好きな言葉を3行表示するプログラムを書く
              3. わざとエラーを起こして、エラーメッセージを読む

              まとめ

              • fn main() { } はプログラムの入口
              • println!() で文字を表示
              • ; で文を終わる
              • cargo run でコンパイル+実行
              • エラーメッセージを読む習慣をつけよう

              確認テスト

              Q1. Rustプログラムの実行が始まる場所はどこ?

              Q2. println!! は何を意味する?

              Q3. 以下のコードの出力は?
              fn main() { println!("A"); println!("B"); println!("C"); }

              Q4. println!("Hello, Rust!) のエラーは何?

              Q5. コンパイル+実行を一度に行うコマンドは?


              Phase 0 完了!

              おめでとうございます!Phase 0を完了しました。

              学んだこと:

              • コンピュータの基本構造(CPU、メモリ、ストレージ)
              • プログラミングとは何か
              • コンパイルとインタプリタの違い
              • Rust開発環境の構築
              • はじめてのRustプログラム

              次のPhase: Phase 1: Rust基礎

              変数と型

              プログラムでデータを扱う基本、「変数」と「型」について学びます。

              変数とは

              変数は「データを入れておく箱」です。

              fn main() {
                  let age = 25;  // ageという変数に25を入れる
                  println!("年齢: {}", age);
              }

              出力:

              年齢: 25
              

              変数の宣言

              #![allow(unused)]
              fn main() {
              let 変数名 = 値;
              }
              • let: 変数を作るキーワード
              • 変数名: 好きな名前をつけられる(ルールあり)
              • =: 値を代入する
              • : 変数に入れるデータ

              変数名のルール

              #![allow(unused)]
              fn main() {
              // OK
              let age = 25;
              let user_name = "太郎";
              let totalCount = 100;
              let _temp = 0;
              
              // NG
              let 123abc = 10;   // 数字で始まってはいけない
              let my-name = "";  // ハイフンは使えない
              let let = 5;       // 予約語は使えない
              }

              慣習: Rustではsnake_case(小文字とアンダースコア)が推奨されます。

              #![allow(unused)]
              fn main() {
              let user_name = "太郎";     // 推奨
              let userName = "太郎";      // 動くけど警告が出る
              }

              型(Type)とは

              型は「データの種類」です。

              #![allow(unused)]
              fn main() {
              let age = 25;           // 整数型
              let height = 170.5;     // 浮動小数点型
              let name = "太郎";       // 文字列型
              let is_student = true;  // 真偽型
              }

              Rustは静的型付け言語で、すべての変数には型があります。

              基本的な型

              整数型

              サイズ範囲
              i88bit-128 〜 127
              i1616bit-32,768 〜 32,767
              i3232bit約-21億 〜 約21億(デフォルト
              i6464bitとても大きい
              u88bit0 〜 255
              u1616bit0 〜 65,535
              u3232bit0 〜 約42億
              u6464bitとても大きい
              • i: 符号あり(マイナスも扱える)
              • u: 符号なし(0以上のみ)
              #![allow(unused)]
              fn main() {
              let age: i32 = 25;
              let count: u32 = 100;
              }

              通常は型を書かなくてもRustが推論してくれます:

              #![allow(unused)]
              fn main() {
              let age = 25;  // i32と推論される
              }

              浮動小数点型

              サイズ説明
              f3232bit単精度
              f6464bit倍精度(デフォルト
              #![allow(unused)]
              fn main() {
              let height = 170.5;      // f64と推論
              let weight: f32 = 65.0;  // 明示的にf32
              }

              真偽型(bool)

              #![allow(unused)]
              fn main() {
              let is_student = true;
              let is_adult = false;
              }

              true(真)またはfalse(偽)の2値のみ。

              文字型(char)

              #![allow(unused)]
              fn main() {
              let c = 'A';
              let emoji = '😀';
              let kanji = '漢';
              }

              シングルクォート'で囲む。1文字だけ。

              文字列型

              #![allow(unused)]
              fn main() {
              let name = "太郎";  // &str型(文字列スライス)
              let greeting = String::from("こんにちは");  // String型
              }

              ダブルクォート"で囲む。文字列は後で詳しく学びます。

              型注釈

              型を明示的に書くことを型注釈といいます:

              #![allow(unused)]
              fn main() {
              let age: i32 = 25;
              let height: f64 = 170.5;
              let name: &str = "太郎";
              let is_student: bool = true;
              }

              通常はRustが推論してくれるので省略できますが、書いた方がわかりやすい場合もあります。

              不変(immutable)と可変(mutable)

              デフォルトは不変

              Rustの変数はデフォルトで不変です。

              fn main() {
                  let x = 5;
                  x = 6;  // エラー!
              }

              エラー:

              error[E0384]: cannot assign twice to immutable variable `x`
              

              可変にするには mut

              fn main() {
                  let mut x = 5;  // mutをつける
                  println!("x = {}", x);
                  x = 6;  // OK!
                  println!("x = {}", x);
              }

              出力:

              x = 5
              x = 6
              

              なぜデフォルトが不変なのか?

              • バグを減らせる:意図しない変更を防ぐ
              • コードが読みやすい:変わらないことが保証される
              • 並行処理で安全:複数の処理が同時にアクセスしても安全

              「変更する必要がある」ときだけ mut をつける習慣をつけましょう。

              シャドーイング

              同じ名前の変数を再度宣言することをシャドーイングといいます:

              fn main() {
                  let x = 5;
                  let x = x + 1;  // 新しいxで古いxを「覆い隠す」
                  let x = x * 2;
                  println!("x = {}", x);  // 12
              }

              mutとの違い:

              • シャドーイング:新しい変数を作る。型も変えられる
              • mut:同じ変数の値を変える。型は変えられない
              #![allow(unused)]
              fn main() {
              // シャドーイング:型を変えられる
              let spaces = "   ";      // &str
              let spaces = spaces.len(); // usize(型が変わった)
              
              // mut:型は変えられない
              let mut spaces = "   ";
              spaces = spaces.len();  // エラー!型が違う
              }

              定数

              変わらない値には定数を使います:

              const MAX_POINTS: u32 = 100_000;
              
              fn main() {
                  println!("最大ポイント: {}", MAX_POINTS);
              }

              定数の特徴:

              • constキーワードを使う
              • 型注釈が必須
              • 大文字とアンダースコアで命名(SCREAMING_SNAKE_CASE
              • プログラム全体で使える

              まとめ

              概念説明
              変数データを入れる箱。letで作る
              データの種類(i32, f64, bool, charなど)
              不変デフォルト。変更できない
              可変mutをつけると変更可能
              シャドーイング同名の変数を再宣言
              定数constで宣言。変更不可

              確認テスト

              Q1. Rustの変数のデフォルトの性質は?

              Q2. 以下の型のうち、符号なし整数型はどれ?

              Q3. 以下のコードの出力は?
              let x = 5; let x = x + 1; let x = x * 2; println!("{}", x);

              Q4. let count = 0; count = count + 1; このコードのエラーを修正するには?

              Q5. 「身長170.5cm」を格納するのに最適な型は?


              次のドキュメント: 02_operators.md

              演算子

              計算や比較を行うための「演算子」について学びます。

              算術演算子

              数値の計算に使います。

              fn main() {
                  let a = 10;
                  let b = 3;
              
                  println!("足し算: {} + {} = {}", a, b, a + b);  // 13
                  println!("引き算: {} - {} = {}", a, b, a - b);  // 7
                  println!("掛け算: {} * {} = {}", a, b, a * b);  // 30
                  println!("割り算: {} / {} = {}", a, b, a / b);  // 3(整数同士は切り捨て)
                  println!("剰余:   {} % {} = {}", a, b, a % b);  // 1(余り)
              }
              演算子意味
              +足し算5 + 38
              -引き算5 - 32
              *掛け算5 * 315
              /割り算5 / 31
              %剰余(余り)5 % 32

              整数の割り算に注意

              整数同士の割り算は小数点以下が切り捨てられます:

              fn main() {
                  let x = 10 / 3;  // 3(3.333...ではない)
                  println!("{}", x);
              }

              小数点以下が必要な場合は浮動小数点数を使います:

              fn main() {
                  let x = 10.0 / 3.0;  // 3.333...
                  println!("{}", x);
              }

              型が違うと計算できない

              fn main() {
                  let a: i32 = 10;
                  let b: f64 = 3.0;
                  // let c = a + b;  // エラー!型が違う
              
                  let c = a as f64 + b;  // aをf64に変換してから計算
                  println!("{}", c);  // 13.0
              }

              比較演算子

              2つの値を比較して、trueまたはfalseを返します。

              fn main() {
                  let a = 5;
                  let b = 3;
              
                  println!("{} == {} は {}", a, b, a == b);  // false
                  println!("{} != {} は {}", a, b, a != b);  // true
                  println!("{} >  {} は {}", a, b, a > b);   // true
                  println!("{} <  {} は {}", a, b, a < b);   // false
                  println!("{} >= {} は {}", a, b, a >= b);  // true
                  println!("{} <= {} は {}", a, b, a <= b);  // false
              }
              演算子意味
              ==等しい5 == 5true
              !=等しくない5 != 3true
              >より大きい5 > 3true
              <より小さい5 < 3false
              >=以上5 >= 5true
              <=以下5 <= 3false

              ===の違い

              #![allow(unused)]
              fn main() {
              let x = 5;   // 代入(xに5を入れる)
              x == 5       // 比較(xが5と等しいかチェック)
              }

              初心者がよく間違えるポイントです!

              論理演算子

              bool値を組み合わせます。

              fn main() {
                  let a = true;
                  let b = false;
              
                  println!("AND: {} && {} = {}", a, b, a && b);  // false
                  println!("OR:  {} || {} = {}", a, b, a || b);  // true
                  println!("NOT: !{} = {}", a, !a);              // false
              }
              演算子意味説明
              &&AND(かつ)両方trueならtrue
              ||OR(または)どちらかtrueならtrue
              !NOT(否定)trueとfalseを反転

              真理値表

              AND (&&)

              aba && b
              truetruetrue
              truefalsefalse
              falsetruefalse
              falsefalsefalse

              OR (||)

              aba || b
              truetruetrue
              truefalsetrue
              falsetruetrue
              falsefalsefalse

              NOT (!)

              a!a
              truefalse
              falsetrue

              実用例

              fn main() {
                  let age = 20;
                  let has_id = true;
              
                  // 20歳以上 かつ IDを持っている
                  let can_enter = age >= 20 && has_id;
                  println!("入場可能: {}", can_enter);  // true
              
                  // 65歳以上 または 12歳未満
                  let is_discount = age >= 65 || age < 12;
                  println!("割引対象: {}", is_discount);  // false
              }

              複合代入演算子

              計算と代入を同時に行います。

              fn main() {
                  let mut x = 10;
              
                  x += 5;   // x = x + 5 と同じ
                  println!("x += 5 → {}", x);  // 15
              
                  x -= 3;   // x = x - 3 と同じ
                  println!("x -= 3 → {}", x);  // 12
              
                  x *= 2;   // x = x * 2 と同じ
                  println!("x *= 2 → {}", x);  // 24
              
                  x /= 4;   // x = x / 4 と同じ
                  println!("x /= 4 → {}", x);  // 6
              
                  x %= 4;   // x = x % 4 と同じ
                  println!("x %= 4 → {}", x);  // 2
              }
              演算子意味
              +=足して代入
              -=引いて代入
              *=掛けて代入
              /=割って代入
              %=剰余を代入

              演算子の優先順位

              数学と同じで、掛け算・割り算が足し算・引き算より先に計算されます。

              fn main() {
                  let result = 2 + 3 * 4;  // 2 + 12 = 14
                  println!("{}", result);
              
                  let result = (2 + 3) * 4;  // 5 * 4 = 20
                  println!("{}", result);
              }

              迷ったらカッコを使いましょう。読みやすさも向上します。

              まとめ

              種類演算子用途
              算術+, -, *, /, %数値計算
              比較==, !=, >, <, >=, <=値の比較
              論理&&, ||, !条件の組み合わせ
              複合代入+=, -=, *=, /=, %=計算して代入

              確認テスト

              Q1. 10 / 3 の結果は?(両方とも整数)

              Q2. true && false の結果は?

              Q3. 以下のコードの出力は?
              let mut x = 5; x += 3; x *= 2; println!("{}", x);

              Q4. let a: i32 = 10; let b: f64 = 2.5; let c = a + b; のエラーを修正するには?

              Q5. 「18歳以上 かつ 身長150cm以上」を判定する条件式は?


              次のドキュメント: 03_control_flow.md

              制御フロー

              プログラムの流れを制御する「条件分岐」と「繰り返し」について学びます。

              if式(条件分岐)

              条件によって処理を分けます。

              基本形

              fn main() {
                  let age = 20;
              
                  if age >= 18 {
                      println!("成人です");
                  }
              }

              if-else

              fn main() {
                  let age = 15;
              
                  if age >= 18 {
                      println!("成人です");
                  } else {
                      println!("未成年です");
                  }
              }

              if-else if-else

              fn main() {
                  let score = 75;
              
                  if score >= 90 {
                      println!("優");
                  } else if score >= 70 {
                      println!("良");
                  } else if score >= 50 {
                      println!("可");
                  } else {
                      println!("不可");
                  }
              }

              条件は必ずbool型

              fn main() {
                  let number = 3;
              
                  // if number {  // エラー!numberはi32でboolではない
                  if number != 0 {  // OK
                      println!("ゼロではない");
                  }
              }

              他の言語(JavaScript、Pythonなど)では if number が動くこともありますが、Rustでは必ず bool 型が必要です。

              ifは「式」である

              Rustでは if は値を返す「式」です。

              fn main() {
                  let condition = true;
                  let number = if condition { 5 } else { 6 };
                  println!("number = {}", number);  // 5
              }

              注意: 両方のブロックが同じ型を返す必要があります。

              #![allow(unused)]
              fn main() {
              // エラー!型が違う
              // let number = if condition { 5 } else { "six" };
              }

              loop(無限ループ)

              loop は明示的に止めるまで繰り返します。

              fn main() {
                  let mut count = 0;
              
                  loop {
                      count += 1;
                      println!("カウント: {}", count);
              
                      if count >= 5 {
                          break;  // ループを抜ける
                      }
                  }
              }

              breakで値を返す

              fn main() {
                  let mut counter = 0;
              
                  let result = loop {
                      counter += 1;
              
                      if counter == 10 {
                          break counter * 2;  // 20を返す
                      }
                  };
              
                  println!("結果: {}", result);  // 20
              }

              while(条件付きループ)

              条件が true の間、繰り返します。

              fn main() {
                  let mut number = 3;
              
                  while number != 0 {
                      println!("{}!", number);
                      number -= 1;
                  }
              
                  println!("発射!");
              }

              出力:

              3!
              2!
              1!
              発射!
              

              for(範囲ループ)

              最もよく使うループです。

              範囲を使った繰り返し

              fn main() {
                  // 1から4まで(5は含まない)
                  for i in 1..5 {
                      println!("{}", i);
                  }
              }

              出力:

              1
              2
              3
              4
              

              範囲の種類

              fn main() {
                  // 1..5  : 1, 2, 3, 4(5を含まない)
                  for i in 1..5 {
                      print!("{} ", i);
                  }
                  println!();
              
                  // 1..=5 : 1, 2, 3, 4, 5(5を含む)
                  for i in 1..=5 {
                      print!("{} ", i);
                  }
                  println!();
              }

              配列の繰り返し

              fn main() {
                  let numbers = [10, 20, 30, 40, 50];
              
                  for n in numbers {
                      println!("{}", n);
                  }
              }

              インデックス付きで繰り返し

              fn main() {
                  let fruits = ["りんご", "みかん", "バナナ"];
              
                  for (index, fruit) in fruits.iter().enumerate() {
                      println!("{}: {}", index, fruit);
                  }
              }

              出力:

              0: りんご
              1: みかん
              2: バナナ
              

              breakとcontinue

              break(ループを抜ける)

              fn main() {
                  for i in 1..10 {
                      if i == 5 {
                          break;  // ループ終了
                      }
                      println!("{}", i);
                  }
              }

              出力:

              1
              2
              3
              4
              

              continue(次の繰り返しへ)

              fn main() {
                  for i in 1..10 {
                      if i % 2 == 0 {
                          continue;  // 偶数はスキップ
                      }
                      println!("{}", i);
                  }
              }

              出力:

              1
              3
              5
              7
              9
              

              match(パターンマッチング)

              複数の条件を整理して書けます。

              fn main() {
                  let number = 3;
              
                  match number {
                      1 => println!("一"),
                      2 => println!("二"),
                      3 => println!("三"),
                      _ => println!("その他"),  // _ はそれ以外すべて
                  }
              }

              複数の値をまとめる

              fn main() {
                  let number = 5;
              
                  match number {
                      1 | 2 | 3 => println!("小さい"),  // 1, 2, 3のどれか
                      4..=6 => println!("中くらい"),     // 4〜6の範囲
                      _ => println!("大きい"),
                  }
              }

              matchは式

              fn main() {
                  let number = 3;
              
                  let description = match number {
                      1 => "一つ",
                      2 => "二つ",
                      _ => "たくさん",
                  };
              
                  println!("{}", description);
              }

              まとめ

              構文用途
              if条件分岐
              loop無限ループ(breakで終了)
              while条件が真の間ループ
              for範囲や配列を繰り返し
              matchパターンマッチング
              breakループを抜ける
              continue次の繰り返しへスキップ

              確認テスト

              Q1. for i in 1..5i が取る値は?

              Q2. continue の動作として正しいのは?

              Q3. 以下のコードの出力は?
              let mut sum = 0; for i in 1..=3 { sum += i; } println!("{}", sum);

              Q4. if number { ... } でnumberがi32型の場合、どう修正すべき?

              Q5. 3の倍数を判定する条件式として正しいのは?


              次のドキュメント: 04_functions.md

              関数

              処理をまとめて再利用可能にする「関数」について学びます。

              関数とは

              関数は「処理をまとめた部品」です。

              fn main() {
                  greet();  // 関数を呼び出す
              }
              
              fn greet() {
                  println!("こんにちは!");
              }

              なぜ関数を使うのか

              1. 再利用: 同じ処理を何度も書かなくて済む
              2. 整理: コードを意味のある単位に分割
              3. テスト: 部品ごとにテストしやすい

              関数の定義

              #![allow(unused)]
              fn main() {
              fn 関数名() {
                  // 処理
              }
              }
              • fn: 関数を定義するキーワード
              • 関数名: snake_case で命名
              • (): 引数リスト
              • {}: 関数の本体

              命名規則

              #![allow(unused)]
              fn main() {
              fn calculate_total() { }   // OK: snake_case
              fn calculateTotal() { }    // 動くが警告が出る
              }

              Rustでは関数名に snake_case(小文字とアンダースコア)を使います。

              引数

              関数にデータを渡せます。

              fn main() {
                  greet("太郎");
                  greet("花子");
              }
              
              fn greet(name: &str) {
                  println!("こんにちは、{}さん!", name);
              }

              出力:

              こんにちは、太郎さん!
              こんにちは、花子さん!
              

              引数の書き方

              #![allow(unused)]
              fn main() {
              fn 関数名(引数名: 型) {
                  // 処理
              }
              }

              型注釈は必須です。省略できません。

              複数の引数

              fn main() {
                  print_sum(5, 3);
              }
              
              fn print_sum(a: i32, b: i32) {
                  println!("{} + {} = {}", a, b, a + b);
              }

              戻り値

              関数は値を返すことができます。

              fn main() {
                  let result = add(5, 3);
                  println!("結果: {}", result);
              }
              
              fn add(a: i32, b: i32) -> i32 {
                  a + b  // セミコロンなし = この値を返す
              }

              戻り値の書き方

              #![allow(unused)]
              fn main() {
              fn 関数名(引数) -> 戻り値の型 {
                  戻り値  // セミコロンなし
              }
              }
              • ->: 戻り値の型を示す
              • 最後の式の値が返される(セミコロンをつけない)

              return を使う方法

              #![allow(unused)]
              fn main() {
              fn add(a: i32, b: i32) -> i32 {
                  return a + b;  // 明示的にreturn
              }
              }

              return を使うと、関数の途中でも値を返して終了できます:

              #![allow(unused)]
              fn main() {
              fn absolute(n: i32) -> i32 {
                  if n < 0 {
                      return -n;  // ここで終了
                  }
                  n  // nが0以上の場合
              }
              }

              式と文

              Rustではセミコロンの有無が重要です。

              fn main() {
                  let x = {
                      let y = 3;
                      y + 1  // セミコロンなし → これが式の値になる
                  };
                  println!("{}", x);  // 4
              }
              fn main() {
                  let x = {
                      let y = 3;
                      y + 1;  // セミコロンあり → 値を返さない(()を返す)
                  };
                  // xは () 型になる
              }
              式(Expression)文(Statement)
              セミコロンなしあり
              値を返すはいいいえ
              5 + 3, x * 2let x = 5;, x = 3;

              関数を使った例

              例1: 面積の計算

              fn main() {
                  let width = 10;
                  let height = 5;
                  let area = calculate_area(width, height);
                  println!("面積: {}", area);
              }
              
              fn calculate_area(width: i32, height: i32) -> i32 {
                  width * height
              }

              例2: 偶数判定

              fn main() {
                  for i in 1..=10 {
                      if is_even(i) {
                          println!("{}は偶数", i);
                      }
                  }
              }
              
              fn is_even(n: i32) -> bool {
                  n % 2 == 0
              }

              例3: 最大値を求める

              fn main() {
                  let max = maximum(10, 25);
                  println!("最大値: {}", max);
              }
              
              fn maximum(a: i32, b: i32) -> i32 {
                  if a > b {
                      a
                  } else {
                      b
                  }
              }

              関数の設計指針

              1. 一つのことをうまくやる

              #![allow(unused)]
              fn main() {
              // 良い例:一つのことに集中
              fn calculate_tax(price: i32) -> i32 {
                  price / 10
              }
              
              fn calculate_total(price: i32, tax: i32) -> i32 {
                  price + tax
              }
              
              // 悪い例:複数のことをやっている
              fn calculate_and_print_total(price: i32) {
                  let tax = price / 10;
                  let total = price + tax;
                  println!("税込: {}", total);
              }
              }

              2. 適切な名前をつける

              #![allow(unused)]
              fn main() {
              // 良い例:何をするかわかる
              fn calculate_area(width: i32, height: i32) -> i32 { ... }
              fn is_valid_email(email: &str) -> bool { ... }
              
              // 悪い例:わかりにくい
              fn calc(a: i32, b: i32) -> i32 { ... }
              fn check(s: &str) -> bool { ... }
              }

              3. 引数は少なめに

              引数が多すぎると使いにくくなります。3〜4個を超えたら構造体の使用を検討。

              まとめ

              要素説明
              fn関数を定義
              引数関数に渡すデータ。型注釈必須
              ->戻り値の型を示す
              戻り値最後の式(セミコロンなし)が返る
              return明示的に値を返す

              確認テスト

              Q1. 関数の戻り値について正しいのは?

              Q2. 関数の引数について正しいのは?

              Q3. double(triple(2)) の結果は?(double: n*2, triple: n*3)

              Q4. fn add(a: i32, b: i32) -> i32 { a + b; } のエラーを修正するには?

              Q5. FizzBuzz問題で「15」は何と出力される?


              次のドキュメント: 05_collections_basic.md

              コレクション基礎

              複数のデータをまとめて扱う「配列」「タプル」「Vec」について学びます。

              配列(Array)

              同じ型のデータを固定長で並べたものです。

              配列の作成

              fn main() {
                  let numbers = [1, 2, 3, 4, 5];
                  println!("{:?}", numbers);
              }

              型と長さの指定

              #![allow(unused)]
              fn main() {
              let numbers: [i32; 5] = [1, 2, 3, 4, 5];
              //           型   長さ
              }

              同じ値で初期化

              #![allow(unused)]
              fn main() {
              let zeros = [0; 5];  // [0, 0, 0, 0, 0]
              }

              要素へのアクセス

              fn main() {
                  let numbers = [10, 20, 30, 40, 50];
              
                  println!("1番目: {}", numbers[0]);  // 10
                  println!("3番目: {}", numbers[2]);  // 30
                  println!("長さ: {}", numbers.len()); // 5
              }

              インデックスは0から始まります!

              配列の繰り返し

              fn main() {
                  let numbers = [1, 2, 3, 4, 5];
              
                  for n in numbers {
                      println!("{}", n);
                  }
              }

              配列の制限

              • 長さが固定: 作成後に増減できない
              • 同じ型のみ: 異なる型は混ぜられない
              #![allow(unused)]
              fn main() {
              // エラー:長さが違う
              // let arr: [i32; 5] = [1, 2, 3];
              
              // エラー:型が混在
              // let arr = [1, "two", 3];
              }

              タプル(Tuple)

              異なる型のデータをまとめられます。

              タプルの作成

              fn main() {
                  let person = ("太郎", 25, 170.5);
                  println!("{:?}", person);
              }

              型の指定

              #![allow(unused)]
              fn main() {
              let person: (&str, i32, f64) = ("太郎", 25, 170.5);
              }

              要素へのアクセス

              fn main() {
                  let person = ("太郎", 25, 170.5);
              
                  println!("名前: {}", person.0);  // 太郎
                  println!("年齢: {}", person.1);  // 25
                  println!("身長: {}", person.2);  // 170.5
              }

              分解(Destructuring)

              fn main() {
                  let person = ("太郎", 25, 170.5);
              
                  let (name, age, height) = person;
                  println!("{}さんは{}歳、身長{}cm", name, age, height);
              }

              関数の複数戻り値として使う

              fn main() {
                  let (min, max) = min_max(5, 10, 3);
                  println!("最小: {}, 最大: {}", min, max);
              }
              
              fn min_max(a: i32, b: i32, c: i32) -> (i32, i32) {
                  let min = if a < b && a < c { a } else if b < c { b } else { c };
                  let max = if a > b && a > c { a } else if b > c { b } else { c };
                  (min, max)
              }

              Vec(ベクター)

              可変長の配列です。要素を追加・削除できます。

              Vecの作成

              fn main() {
                  // 空のVec
                  let mut numbers: Vec<i32> = Vec::new();
              
                  // マクロを使った作成
                  let numbers = vec![1, 2, 3, 4, 5];
              
                  println!("{:?}", numbers);
              }

              要素の追加

              fn main() {
                  let mut numbers = Vec::new();
              
                  numbers.push(1);
                  numbers.push(2);
                  numbers.push(3);
              
                  println!("{:?}", numbers);  // [1, 2, 3]
              }

              要素の削除

              fn main() {
                  let mut numbers = vec![1, 2, 3, 4, 5];
              
                  let last = numbers.pop();  // 最後の要素を取り出す
                  println!("取り出した値: {:?}", last);  // Some(5)
                  println!("残り: {:?}", numbers);        // [1, 2, 3, 4]
              }

              要素へのアクセス

              fn main() {
                  let numbers = vec![10, 20, 30, 40, 50];
              
                  // インデックスでアクセス(範囲外でパニック)
                  println!("{}", numbers[0]);  // 10
              
                  // getでアクセス(範囲外でNone)
                  match numbers.get(10) {
                      Some(n) => println!("{}", n),
                      None => println!("範囲外"),
                  }
              }

              Vecの繰り返し

              fn main() {
                  let numbers = vec![1, 2, 3, 4, 5];
              
                  for n in &numbers {  // 参照で繰り返す
                      println!("{}", n);
                  }
              
                  println!("Vecはまだ使える: {:?}", numbers);
              }

              Vecの便利なメソッド

              fn main() {
                  let numbers = vec![1, 2, 3, 4, 5];
              
                  println!("長さ: {}", numbers.len());       // 5
                  println!("空?: {}", numbers.is_empty());   // false
                  println!("含む?: {}", numbers.contains(&3)); // true
              }

              比較表

              特徴配列タプルVec
              長さ固定固定可変
              同じ型のみ異なる型OK同じ型のみ
              アクセスarr[i]tuple.ivec[i] or vec.get(i)
              用途固定サイズのデータ複数の戻り値動的なリスト

              使い分け

              配列を使う場面

              • データ数が決まっている
              • 変更する必要がない
              #![allow(unused)]
              fn main() {
              let days = ["月", "火", "水", "木", "金", "土", "日"];
              }

              タプルを使う場面

              • 異なる型のデータをまとめる
              • 関数から複数の値を返す
              #![allow(unused)]
              fn main() {
              fn get_user() -> (&str, i32) {
                  ("太郎", 25)
              }
              }

              Vecを使う場面

              • データ数が変わる
              • 要素を追加・削除する
              #![allow(unused)]
              fn main() {
              let mut todo_list = vec!["買い物", "掃除"];
              todo_list.push("洗濯");
              }

              まとめ

              コレクション作成長さ型の制限
              配列[1, 2, 3]固定同じ型のみ
              タプル(1, "a", true)固定異なる型OK
              Vecvec![1, 2, 3]可変同じ型のみ

              確認テスト

              Q1. 配列とVecの違いは?

              Q2. タプルの要素にアクセスする方法は?

              Q3. 以下のコードの出力は?
              let mut v = vec![1, 2, 3]; v.push(4); v.pop(); v.push(5); println!("{:?}", v);

              Q4. let numbers = vec![1, 2, 3]; numbers.push(4); のエラーを修正するには?

              Q5. 空のVecに1から10までの偶数を追加した場合、合計はいくつ?


              次のドキュメント: 06_error_handling_intro.md

              エラーハンドリング入門

              プログラムで起こりうるエラーを適切に処理する方法を学びます。

              Rustのエラー処理の考え方

              多くの言語では「例外」を使いますが、Rustは型システムでエラーを扱います。

              他の言語:  成功 or 例外(突然プログラムが止まる)
              Rust:     Result<成功, 失敗> を返す(呼び出し側が処理を決める)
              

              これにより、エラーを「忘れて無視する」ことが難しくなります。

              Option型:値があるかないか

              Optionは「値があるかもしれないし、ないかもしれない」を表します。

              Option の定義(イメージ)

              #![allow(unused)]
              fn main() {
              enum Option<T> {
                  Some(T),  // 値がある
                  None,     // 値がない
              }
              }

              使用例

              fn main() {
                  let numbers = vec![1, 2, 3];
              
                  // インデックス1の要素を取得
                  match numbers.get(1) {
                      Some(n) => println!("見つかった: {}", n),
                      None => println!("見つからない"),
                  }
              
                  // インデックス10の要素を取得(範囲外)
                  match numbers.get(10) {
                      Some(n) => println!("見つかった: {}", n),
                      None => println!("見つからない"),
                  }
              }

              出力:

              見つかった: 2
              見つからない
              

              なぜ Option を使うのか

              #![allow(unused)]
              fn main() {
              // 他の言語でよくあるパターン(危険)
              // let value = array[10];  // 範囲外 → クラッシュ!
              
              // Rustのパターン(安全)
              let value = numbers.get(10);  // Option<&i32> を返す
              // 必ず Some/None を処理する必要がある
              }

              Option の便利なメソッド

              fn main() {
                  let some_number = Some(5);
                  let no_number: Option<i32> = None;
              
                  // unwrap_or: Noneの場合のデフォルト値
                  println!("{}", some_number.unwrap_or(0));  // 5
                  println!("{}", no_number.unwrap_or(0));    // 0
              
                  // is_some / is_none
                  println!("{}", some_number.is_some());  // true
                  println!("{}", no_number.is_none());    // true
              }

              Result型:成功か失敗か

              Resultは「処理が成功したか失敗したか」を表します。

              Result の定義(イメージ)

              #![allow(unused)]
              fn main() {
              enum Result<T, E> {
                  Ok(T),   // 成功(Tは成功時の値)
                  Err(E),  // 失敗(Eはエラー情報)
              }
              }

              使用例

              fn main() {
                  let result = divide(10, 2);
                  match result {
                      Ok(value) => println!("結果: {}", value),
                      Err(e) => println!("エラー: {}", e),
                  }
              
                  let result = divide(10, 0);
                  match result {
                      Ok(value) => println!("結果: {}", value),
                      Err(e) => println!("エラー: {}", e),
                  }
              }
              
              fn divide(a: i32, b: i32) -> Result<i32, String> {
                  if b == 0 {
                      Err(String::from("ゼロで割ることはできません"))
                  } else {
                      Ok(a / b)
                  }
              }

              出力:

              結果: 5
              エラー: ゼロで割ることはできません
              

              ファイル操作の例

              use std::fs;
              
              fn main() {
                  let result = fs::read_to_string("hello.txt");
              
                  match result {
                      Ok(content) => println!("内容: {}", content),
                      Err(e) => println!("読み込みエラー: {}", e),
                  }
              }

              ファイルが存在しない場合、Errが返されます。

              unwrapとexpect(注意が必要)

              unwrap

              SomeまたはOkの値を取り出します。NoneErrの場合はパニック(プログラム停止)。

              fn main() {
                  let some_value = Some(5);
                  let value = some_value.unwrap();  // 5
                  println!("{}", value);
              
                  // let none_value: Option<i32> = None;
                  // let value = none_value.unwrap();  // パニック!
              }

              expect

              unwrapと同じですが、エラーメッセージを指定できます。

              fn main() {
                  let result: Result<i32, &str> = Err("何か問題が発生");
                  // let value = result.expect("致命的なエラー");  // パニック!メッセージ付き
              }

              unwrap/expectを使っていい場面

              • 学習中・プロトタイプ: 素早く動かしたいとき
              • 絶対に失敗しない場合: 論理的に失敗しないことが保証されるとき
              #![allow(unused)]
              fn main() {
              // OK: "42"は必ず数値に変換できる
              let number: i32 = "42".parse().unwrap();
              }

              本番コードでは避ける

              #![allow(unused)]
              fn main() {
              // 避ける
              let content = fs::read_to_string("config.txt").unwrap();
              
              // 推奨
              let content = fs::read_to_string("config.txt")
                  .expect("設定ファイルの読み込みに失敗");  // せめてメッセージを
              
              // より良い: エラーを伝播
              fn read_config() -> Result<String, std::io::Error> {
                  fs::read_to_string("config.txt")
              }
              }

              if letによる簡潔な処理

              特定のパターンだけ処理したい場合に便利です。

              fn main() {
                  let some_value = Some(5);
              
                  // matchを使う場合
                  match some_value {
                      Some(n) => println!("値: {}", n),
                      None => {}  // 何もしない
                  }
              
                  // if letを使う場合(簡潔)
                  if let Some(n) = some_value {
                      println!("値: {}", n);
                  }
              }

              まとめ

              用途バリアント
              Option<T>値の有無Some(T), None
              Result<T, E>成功/失敗Ok(T), Err(E)
              メソッド動作注意
              unwrap()値を取り出す失敗でパニック
              expect("msg")値を取り出す失敗でメッセージ付きパニック
              unwrap_or(default)値またはデフォルト安全
              is_some() / is_ok()成功かチェック安全

              確認テスト

              Q1. Option<T>Noneは何を表す?

              Q2. unwrap()を使う際のリスクは?

              Q3. 以下のコードの出力は?
              let numbers = vec![10, 20, 30]; let value = numbers.get(5).unwrap_or(&0); println!("{}", value);

              Q4. unwrap()を使わずに安全にOptionを処理する方法として適切でないのは?

              Q5. safe_divide(10, 0)がNoneを返す関数を実装する場合、正しいのは?


              Phase 1 完了!

              おめでとうございます!Phase 1を完了しました。

              学んだこと:

              • 変数と型(let、mut、基本型)
              • 演算子(算術、比較、論理)
              • 制御フロー(if、loop、while、for、match)
              • 関数(引数、戻り値、式と文)
              • コレクション(配列、タプル、Vec)
              • エラーハンドリング入門(Option、Result)

              次のPhase: Phase 2: 所有権とメモリ

              Phase 2はRust最重要の概念「所有権」を学びます。ここが理解できればRustマスターへの道が開けます!

              スタックとヒープ

              所有権を理解するために、まずメモリの仕組みを学びます。

              メモリの2つの領域

              プログラムが使うメモリには主に2つの領域があります。

              ┌─────────────────────────────────────────┐
              │                メモリ                    │
              │  ┌─────────────┐  ┌─────────────────┐   │
              │  │   スタック   │  │     ヒープ       │   │
              │  │  (Stack)    │  │    (Heap)       │   │
              │  │             │  │                 │   │
              │  │ 高速・固定   │  │ 柔軟・可変       │   │
              │  │ サイズ限定   │  │ サイズ自由       │   │
              │  └─────────────┘  └─────────────────┘   │
              └─────────────────────────────────────────┘
              

              スタック(Stack)

              特徴

              • LIFO(Last In, First Out):最後に入れたものを最初に取り出す
              • 高速:メモリの場所が決まっている
              • 固定サイズ:コンパイル時にサイズがわかるデータのみ

              イメージ:本の積み重ね

                    ↓ push(追加)
                 ┌─────┐
                 │  3  │ ← 最後に追加
                 ├─────┤
                 │  2  │
                 ├─────┤
                 │  1  │ ← 最初に追加
                 └─────┘
                    ↑ pop(取り出し)は上から
              

              スタックに置かれるデータ

              fn main() {
                  let x: i32 = 42;      // スタック(サイズ固定:4バイト)
                  let y: f64 = 3.14;    // スタック(サイズ固定:8バイト)
                  let z: bool = true;   // スタック(サイズ固定:1バイト)
                  let arr: [i32; 3] = [1, 2, 3];  // スタック(サイズ固定:12バイト)
              }

              これらはコンパイル時にサイズがわかるので、スタックに置かれます。

              ヒープ(Heap)

              特徴

              • 動的:実行時にサイズが決まるデータを置ける
              • 柔軟:大きなデータや可変長データを扱える
              • 間接アクセス:ポインタ経由でアクセス

              イメージ:倉庫と伝票

              スタック(伝票)          ヒープ(倉庫)
              ┌─────────┐            ┌─────────────────┐
              │ ptr ────────────────→│ "Hello, Rust!"  │
              │ len: 13 │            └─────────────────┘
              │ cap: 13 │
              └─────────┘
              

              ヒープに置かれるデータ

              fn main() {
                  let s = String::from("Hello");  // ヒープに文字列データ
                  let v = vec![1, 2, 3, 4, 5];    // ヒープに配列データ
              }

              StringVec実行時にサイズが変わる可能性があるので、ヒープに置かれます。

              Stringのメモリレイアウト

              #![allow(unused)]
              fn main() {
              let s = String::from("hello");
              }
              スタック                     ヒープ
              ┌─────────────┐            ┌───┬───┬───┬───┬───┐
              │ ptr ─────────────────────→│ h │ e │ l │ l │ o │
              ├─────────────┤            └───┴───┴───┴───┴───┘
              │ len: 5      │            (5バイトのデータ)
              ├─────────────┤
              │ capacity: 5 │
              └─────────────┘
              
              • ptr: ヒープ上のデータへのポインタ
              • len: 現在の長さ
              • capacity: 確保済みの容量

              なぜ2つの領域があるのか

              スタックの限界

              #![allow(unused)]
              fn main() {
              // これはコンパイルエラー(サイズが不明)
              // let s: str = ???;  // strのサイズは実行時まで不明
              }

              スタックには「コンパイル時にサイズがわかるもの」しか置けません。

              ヒープの必要性

              fn main() {
                  let mut s = String::from("Hello");
                  s.push_str(", World!");  // 文字列が伸びる
                  println!("{}", s);
              }

              Stringは後から長さが変わるので、柔軟なヒープに置く必要があります。

              速度の違い

              アクセス速度の比較(イメージ):
              
              スタック: ████████████████████ 100%
              ヒープ:   ██████████           50%(ポインタ経由のため)
              

              スタックは「次のデータの場所」が決まっているので高速です。 ヒープは「ポインタをたどる」必要があるため、少し遅くなります。

              コピーとムーブ

              スタックのデータ:コピー

              fn main() {
                  let x = 5;
                  let y = x;  // xの値をコピー
              
                  println!("x = {}, y = {}", x, y);  // 両方使える
              }

              スタックのデータは小さいので、そのままコピーされます。

              ヒープのデータ:ムーブ

              fn main() {
                  let s1 = String::from("hello");
                  let s2 = s1;  // s1からs2にムーブ
              
                  // println!("{}", s1);  // エラー!s1は無効
                  println!("{}", s2);     // OK
              }

              ヒープのデータは大きい可能性があるので、所有権が移動します。 これが次のドキュメントで学ぶ「所有権」の核心です。

              まとめ

              特徴スタックヒープ
              サイズ固定(コンパイル時に決定)可変(実行時に決定)
              速度高速比較的遅い
              データ例i32, f64, bool, 配列String, Vec
              代入時コピームーブ(所有権移動)

              確認テスト

              Q1. スタックに置かれるデータの特徴として正しいものはどれですか?

              Q2. String型がヒープにデータを置く理由として正しいものはどれですか?

              Q3. 以下のコードで、ヒープにデータが確保される変数はどれですか?
              let a: i32 = 10; let b = String::from("hi"); let c: [i32; 3] = [1,2,3];

              Q4. 以下のコードがコンパイルエラーになる理由は何ですか?
              let s1 = String::from("hello"); let s2 = s1; println!("{}", s1);

              Q5. let x = 5; let y = x;の実行後、xとyの状態として正しいものはどれですか?


              次のドキュメント: 02_ownership.md

              所有権(Ownership)

              Rust最重要の概念です。これを理解すればRustの大部分が理解できます。

              所有権とは

              Rustでは、すべての値に「所有者」(owner)がいます。所有者はその値に対する責任を持ちます。

              所有権の3つのルール

              ┌──────────────────────────────────────────────────┐
              │             所有権の3つのルール                    │
              ├──────────────────────────────────────────────────┤
              │ 1. Rustの各値は「所有者」と呼ばれる変数を持つ        │
              │ 2. 所有者は同時に1つだけ存在できる                  │
              │ 3. 所有者がスコープを抜けると、値は破棄される         │
              └──────────────────────────────────────────────────┘
              

              この3つのルールがRustのメモリ安全性を支えています。

              ルール1: 各値には所有者がいる

              fn main() {
                  let s = String::from("hello");  // sが"hello"の所有者
                  println!("{}", s);
              }

              s"hello"という文字列の所有者です。

              ルール2: 所有者は同時に1つだけ

              fn main() {
                  let s1 = String::from("hello");
                  let s2 = s1;  // 所有権がs1からs2に移動(ムーブ)
              
                  // println!("{}", s1);  // エラー!s1はもう所有者ではない
                  println!("{}", s2);     // OK
              }

              ムーブの図解

              s1 = String::from("hello");
              
              スタック                     ヒープ
              ┌─────────────┐            ┌───────────┐
              │ s1          │            │           │
              │ ptr ─────────────────────→│ "hello"   │
              │ len: 5      │            │           │
              │ cap: 5      │            └───────────┘
              └─────────────┘
              
              s2 = s1;  // ムーブ後
              
              ┌─────────────┐            ┌───────────┐
              │ s1 (無効)   │            │           │
              │ ptr ────X   │            │ "hello"   │
              │ len: 5      │            │           │
              │ cap: 5      │            └───────────┘
              └─────────────┘                  ↑
              ┌─────────────┐                  │
              │ s2          │                  │
              │ ptr ─────────────────────────────
              │ len: 5      │
              │ cap: 5      │
              └─────────────┘
              

              ルール3: スコープを抜けると値は破棄

              fn main() {
                  {
                      let s = String::from("hello");
                      println!("{}", s);
                  }  // ← ここでsがスコープを抜ける。メモリが解放される。
              
                  // println!("{}", s);  // エラー!sは存在しない
              }

              なぜこれが重要なのか

              C言語などでは、メモリの解放を忘れるとメモリリークが起きます。 逆に、同じメモリを2回解放すると二重解放バグが起きます。

              Rustは所有権ルールにより、これらをコンパイル時に防ぎます

              コピーとムーブ

              コピーされる型(Copy型)

              スタックに収まる小さな型は、代入時にコピーされます。

              fn main() {
                  let x = 5;
                  let y = x;  // コピー
              
                  println!("x = {}, y = {}", x, y);  // 両方使える
              }

              Copy型の例: i32, f64, bool, char, タプル(全要素がCopyの場合)

              ムーブされる型

              ヒープを使う型は、代入時にムーブ(所有権の移動)されます。

              fn main() {
                  let s1 = String::from("hello");
                  let s2 = s1;  // ムーブ
              
                  // println!("{}", s1);  // エラー!
                  println!("{}", s2);
              }

              ムーブされる型の例: String, Vec<T>, Box<T>

              clone: 明示的なコピー

              ヒープのデータもコピーしたい場合は、cloneを使います。

              fn main() {
                  let s1 = String::from("hello");
                  let s2 = s1.clone();  // ヒープのデータもコピー
              
                  println!("s1 = {}, s2 = {}", s1, s2);  // 両方使える
              }
              clone後のメモリ:
              
              スタック                     ヒープ
              ┌─────────────┐            ┌───────────┐
              │ s1          │            │ "hello"   │ ← s1用
              │ ptr ─────────────────────→│           │
              └─────────────┘            └───────────┘
              ┌─────────────┐            ┌───────────┐
              │ s2          │            │ "hello"   │ ← s2用(別の場所)
              │ ptr ─────────────────────→│           │
              └─────────────┘            └───────────┘
              

              注意: cloneはヒープのデータをコピーするので、大きなデータでは遅くなります。

              関数と所有権

              関数に渡すとムーブする

              fn main() {
                  let s = String::from("hello");
                  takes_ownership(s);  // sの所有権が関数に移動
              
                  // println!("{}", s);  // エラー!sは無効
              }
              
              fn takes_ownership(some_string: String) {
                  println!("{}", some_string);
              }  // some_stringがスコープを抜けてメモリ解放

              関数から返すと所有権が移動

              fn main() {
                  let s1 = gives_ownership();  // 所有権をもらう
                  println!("{}", s1);
              
                  let s2 = String::from("hello");
                  let s3 = takes_and_gives_back(s2);  // s2を渡して、新しい所有権をもらう
              
                  // println!("{}", s2);  // エラー!
                  println!("{}", s3);     // OK
              }
              
              fn gives_ownership() -> String {
                  String::from("hello")
              }
              
              fn takes_and_gives_back(s: String) -> String {
                  s
              }

              所有権のまとめ図

              ┌────────────────────────────────────────────────────────┐
              │                    所有権の移動パターン                   │
              ├────────────────────────────────────────────────────────┤
              │                                                        │
              │  変数代入: let s2 = s1;       → s1からs2にムーブ         │
              │                                                        │
              │  関数呼出: func(s);           → sから引数にムーブ         │
              │                                                        │
              │  関数戻値: let s = func();    → 関数からsにムーブ         │
              │                                                        │
              │  clone:   let s2 = s1.clone(); → コピー(両方使える)     │
              │                                                        │
              └────────────────────────────────────────────────────────┘
              

              よくあるエラーと対処

              エラー: use of moved value

              fn main() {
                  let s = String::from("hello");
                  let s2 = s;
                  println!("{}", s);  // error: use of moved value: `s`
              }

              対処法:

              1. cloneを使う: let s2 = s.clone();
              2. 参照を使う(次のドキュメントで学習): let s2 = &s;

              まとめ

              ルール内容
              ルール1各値には所有者がいる
              ルール2所有者は同時に1つだけ
              ルール3スコープを抜けると値は破棄
              操作Copy型ムーブ型
              代入コピームーブ
              関数に渡すコピームーブ
              両方使いたいそのままclone

              確認テスト

              Q1. 所有権のルールとして正しいものはどれですか?

              Q2. 以下のうち、Copy型(代入時にコピーされる型)はどれですか?

              Q3. 以下のコードはコンパイルできますか?
              let s1 = String::from("hello"); let s2 = s1; println!("{}", s1);

              Q4. 関数にStringを渡した後も元の変数を使いたい場合、最も効率的な方法はどれですか?

              Q5. clone()メソッドについて正しい説明はどれですか?


              次のドキュメント: 03_references_borrowing.md

              参照と借用

              所有権を移動させずにデータを使う「参照」と「借用」について学びます。

              問題: 所有権を移動させたくない

              前回の例を思い出してください:

              fn main() {
                  let s = String::from("hello");
                  let len = calculate_length(s);  // 所有権がムーブ
                  // println!("{}", s);  // エラー!
              }
              
              fn calculate_length(s: String) -> usize {
                  s.len()
              }

              値を使いたいだけなのに、所有権を渡さなければいけないのは不便です。

              解決策: 参照(Reference)

              参照を使うと、所有権を移動させずにデータにアクセスできます。

              fn main() {
                  let s = String::from("hello");
                  let len = calculate_length(&s);  // 参照を渡す
                  println!("'{}'の長さは{}", s, len);  // sはまだ使える!
              }
              
              fn calculate_length(s: &String) -> usize {
                  s.len()
              }

              参照の図解

              let s = String::from("hello");
              let r = &s;
              
              スタック                     ヒープ
              ┌─────────────┐
              │ s           │            ┌───────────┐
              │ ptr ─────────────────────→│ "hello"   │
              │ len: 5      │            └───────────┘
              │ cap: 5      │                  ↑
              └─────────────┘                  │
                     ↑                         │
              ┌──────┴──────┐                  │
              │ r (&s)      │                  │
              │ ptr ────────┘ (sを指す)
              └─────────────┘
              

              rs参照しています。sの所有権は移動しません。

              借用(Borrowing)

              参照を作ることを借用(Borrowing)と呼びます。

              本を図書館から借りるイメージです:

              • 図書館(所有者)は本を持っている
              • あなた(借用者)は本を読める
              • 読み終わったら返す(参照のスコープが終わる)
              • 本は図書館のまま(所有権は移動しない)

              不変参照(Immutable Reference)

              &で作る参照は読み取り専用です。

              fn main() {
                  let s = String::from("hello");
                  let r1 = &s;
                  let r2 = &s;  // 複数の不変参照OK
              
                  println!("{}, {}", r1, r2);
              }

              不変参照のルール

              • 複数の不変参照を同時に持てる
              • 参照先のデータを変更できない
              fn main() {
                  let s = String::from("hello");
                  let r = &s;
              
                  // r.push_str(" world");  // エラー!不変参照からは変更できない
              }

              可変参照(Mutable Reference)

              &mutで作る参照は変更可能です。

              fn main() {
                  let mut s = String::from("hello");
                  change(&mut s);
                  println!("{}", s);  // "hello, world"
              }
              
              fn change(s: &mut String) {
                  s.push_str(", world");
              }

              可変参照のルール(重要!)

              ルール: 可変参照は同時に1つだけ

              fn main() {
                  let mut s = String::from("hello");
              
                  let r1 = &mut s;
                  // let r2 = &mut s;  // エラー!可変参照は1つだけ
              
                  println!("{}", r1);
              }

              なぜ1つだけ?

              データ競合を防ぐためです。

              データ競合は以下の3つが同時に起きると発生します:

              1. 2つ以上のポインタが同じデータにアクセス
              2. 少なくとも1つが書き込みを行う
              3. 同期機構がない

              Rustは「可変参照は1つだけ」というルールで、コンパイル時にこれを防ぎます。

              不変参照と可変参照の混在

              不変参照がある間は、可変参照を作れません

              fn main() {
                  let mut s = String::from("hello");
              
                  let r1 = &s;      // OK
                  let r2 = &s;      // OK
                  // let r3 = &mut s;  // エラー!不変参照があるので可変参照は作れない
              
                  println!("{}, {}", r1, r2);
              }

              ただし、不変参照が使われなくなった後なら可変参照を作れます:

              fn main() {
                  let mut s = String::from("hello");
              
                  let r1 = &s;
                  let r2 = &s;
                  println!("{}, {}", r1, r2);  // r1, r2はここまで
              
                  let r3 = &mut s;  // OK!r1, r2はもう使われない
                  println!("{}", r3);
              }

              参照のルールまとめ

              ┌────────────────────────────────────────────────────┐
              │               参照のルール                          │
              ├────────────────────────────────────────────────────┤
              │ 1. 不変参照(&T)は複数同時に存在できる              │
              │ 2. 可変参照(&mut T)は同時に1つだけ                │
              │ 3. 不変参照と可変参照は同時に存在できない            │
              │ 4. 参照は常に有効でなければならない                  │
              └────────────────────────────────────────────────────┘
              

              ダングリング参照

              ダングリング参照とは、解放されたメモリを指す参照です。

              fn main() {
                  let r = dangle();
              }
              
              fn dangle() -> &String {  // コンパイルエラー!
                  let s = String::from("hello");
                  &s  // sへの参照を返そうとしている
              }  // ← sがドロップされる。参照先がなくなる!

              Rustはこれをコンパイル時に防ぎます

              解決策: 所有権を返す

              #![allow(unused)]
              fn main() {
              fn no_dangle() -> String {
                  let s = String::from("hello");
                  s  // 所有権を返す(参照ではなく値自体)
              }
              }

              実践例

              例1: 最長の文字列を返す

              fn main() {
                  let s1 = String::from("hello");
                  let s2 = String::from("hi");
              
                  let result = longest(&s1, &s2);
                  println!("最長: {}", result);
              }
              
              fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
                  if x.len() > y.len() {
                      x
                  } else {
                      y
                  }
              }

              'aはライフタイムで、次のドキュメントで学びます)

              例2: Vecの要素を変更

              fn main() {
                  let mut numbers = vec![1, 2, 3, 4, 5];
              
                  for n in &mut numbers {
                      *n *= 2;  // 各要素を2倍に
                  }
              
                  println!("{:?}", numbers);  // [2, 4, 6, 8, 10]
              }

              まとめ

              種類構文特徴
              不変参照&T読み取り専用、複数OK
              可変参照&mut T変更可能、1つだけ
              ルール内容
              ルール1不変参照は複数OK
              ルール2可変参照は1つだけ
              ルール3不変と可変は同時に存在不可
              ルール4参照は常に有効(ダングリング禁止)

              確認テスト

              Q1. 不変参照(&T)について正しいものはどれですか?

              Q2. 可変参照(&mut T)について正しいものはどれですか?

              Q3. 以下のコードはコンパイルできますか?
              let mut s = String::from("hello"); let r1 = &s; let r2 = &mut s; println!("{}, {}", r1, r2);

              Q4. 関数内で文字列を変更したい場合、引数の型として正しいものはどれですか?

              Q5. 「借用」(Borrowing)の概念について正しい説明はどれですか?


              次のドキュメント: 04_lifetimes_basic.md

              ライフタイム入門

              参照が有効な期間を示す「ライフタイム」について学びます。

              ライフタイムとは

              ライフタイムは「参照が有効な期間」を表します。

              fn main() {
                  let r;                // ---------+-- 'a
                                        //          |
                  {                     //          |
                      let x = 5;        // -+-- 'b  |
                      r = &x;           //  |       |
                  }                     // -+       |  (xが破棄される)
                                        //          |
                  // println!("{}", r); // エラー! |  (rは無効なメモリを指す)
              }                         // ---------+

              xのライフタイム'brのライフタイム'aより短いため、エラーになります。

              なぜライフタイムが必要なのか

              ダングリング参照を防ぐ

              #![allow(unused)]
              fn main() {
              // これはコンパイルエラー
              fn dangle() -> &String {
                  let s = String::from("hello");
                  &s  // sへの参照を返そうとしている
              }  // ← sがドロップされる!参照先がなくなる
              }

              ライフタイムによって、コンパイラは「参照が参照先より長生きしないか」をチェックします。

              関数のライフタイム注釈

              問題のあるコード

              #![allow(unused)]
              fn main() {
              // これはコンパイルエラー
              fn longest(x: &str, y: &str) -> &str {
                  if x.len() > y.len() {
                      x
                  } else {
                      y
                  }
              }
              }

              エラー:

              error[E0106]: missing lifetime specifier
              

              コンパイラは「返される参照がxのライフタイムなのかyのライフタイムなのかわからない」と言っています。

              ライフタイム注釈で解決

              #![allow(unused)]
              fn main() {
              fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
                  if x.len() > y.len() {
                      x
                  } else {
                      y
                  }
              }
              }
              • <'a>: ライフタイムパラメータの宣言
              • &'a str: ライフタイム'aを持つ文字列参照

              この注釈は「返される参照は、xとyの両方が有効な間だけ有効」という意味です。

              ライフタイム注釈の構文

              #![allow(unused)]
              fn main() {
              &i32        // 参照
              &'a i32     // ライフタイム'aを持つ参照
              &'a mut i32 // ライフタイム'aを持つ可変参照
              }

              具体例で理解する

              fn main() {
                  let string1 = String::from("long string is long");
              
                  {
                      let string2 = String::from("xyz");
                      let result = longest(&string1, &string2);
                      println!("最長: {}", result);  // OK: string2はまだ有効
                  }
              
                  // ここでresultを使おうとすると...?
              }
              
              fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
                  if x.len() > y.len() {
                      x
                  } else {
                      y
                  }
              }

              これはOKです。resultは内側のスコープ内でのみ使われており、その時点ではstring1string2も有効だからです。

              エラーになるケース

              fn main() {
                  let string1 = String::from("long string is long");
                  let result;
              
                  {
                      let string2 = String::from("xyz");
                      result = longest(&string1, &string2);
                  }  // string2がドロップされる
              
                  println!("最長: {}", result);  // エラー!string2はもうない
              }

              resultstring2を参照する可能性があるのに、string2はスコープを抜けて無効になっています。

              ライフタイム省略規則

              すべてのケースでライフタイムを書く必要はありません。

              省略できるケース

              #![allow(unused)]
              fn main() {
              // 明示的なライフタイム
              fn first_word<'a>(s: &'a str) -> &'a str { ... }
              
              // 省略可能(コンパイラが推論)
              fn first_word(s: &str) -> &str { ... }
              }

              省略規則(参考)

              1. 各引数の参照は別々のライフタイムを得る
              2. 入力ライフタイムが1つなら、それが出力に適用される
              3. &self&mut selfがある場合、selfのライフタイムが出力に適用される

              最初は「省略できることがある」程度の理解で大丈夫です。

              構造体のライフタイム

              参照を持つ構造体にはライフタイム注釈が必要です。

              struct ImportantExcerpt<'a> {
                  part: &'a str,
              }
              
              fn main() {
                  let novel = String::from("Call me Ishmael. Some years ago...");
                  let first_sentence = novel.split('.').next().unwrap();
              
                  let excerpt = ImportantExcerpt {
                      part: first_sentence,
                  };
              
                  println!("{}", excerpt.part);
              }

              この構造体は「partが参照しているデータが有効な間だけ有効」です。

              ’static ライフタイム

              'staticは「プログラム全体で有効」な特別なライフタイムです。

              #![allow(unused)]
              fn main() {
              let s: &'static str = "Hello, world!";
              }

              文字列リテラルは'staticライフタイムを持ちます。プログラムに埋め込まれているため、常に有効です。

              注意

              'staticを安易に使うのは避けましょう。本当に必要なケースは稀です。

              ライフタイムのメンタルモデル

              ┌────────────────────────────────────────────────────────┐
              │    ライフタイム = 「参照が有効な期間の制約」              │
              ├────────────────────────────────────────────────────────┤
              │                                                        │
              │  目的: ダングリング参照を防ぐ                           │
              │                                                        │
              │  ルール:                                               │
              │  ・参照は参照先より長生きしてはいけない                  │
              │  ・関数が複数の参照を受け取り参照を返す場合、            │
              │    ライフタイム注釈で関係を明示                         │
              │                                                        │
              │  省略: 多くの場合、コンパイラが推論してくれる            │
              │                                                        │
              └────────────────────────────────────────────────────────┘
              

              まとめ

              概念説明
              ライフタイム参照が有効な期間
              'aライフタイムパラメータ
              注釈の目的参照間の関係を明示
              'staticプログラム全体で有効

              最初は難しく感じますが、使っていくうちに自然と理解できるようになります。


              確認テスト

              Q1. ライフタイムの主な目的は何ですか?

              Q2. 'staticライフタイムが意味するものは何ですか?

              Q3. 以下のコードはコンパイルできますか?
              let r; { let x = 5; r = &x; } println!("{}", r);

              Q4. 関数fn longest(x: &str, y: &str) -> &strがコンパイルエラーになる理由は何ですか?

              Q5. 構造体に参照を持たせる場合、必要なものは何ですか?


              次のドキュメント: 05_slices.md

              スライス

              コレクションの一部を参照する「スライス」について学びます。

              スライスとは

              スライスはコレクションの一部への参照です。所有権を持ちません。

              fn main() {
                  let s = String::from("hello world");
              
                  let hello = &s[0..5];   // "hello"へのスライス
                  let world = &s[6..11];  // "world"へのスライス
              
                  println!("{}, {}", hello, world);
              }

              文字列スライス(&str)

              基本的な使い方

              fn main() {
                  let s = String::from("hello world");
              
                  let slice1 = &s[0..5];   // "hello"
                  let slice2 = &s[6..11];  // "world"
                  let slice3 = &s[0..];    // "hello world"(最初から)
                  let slice4 = &s[..5];    // "hello"(最初から5まで)
                  let slice5 = &s[..];     // "hello world"(全体)
              
                  println!("{}", slice1);
              }

              範囲の構文

              構文意味例(“hello“の場合)
              [0..5]0から5未満“hello”
              [..5]最初から5未満“hello”
              [3..]3から最後まで“lo”
              [..]全体“hello”
              [0..=4]0から4まで(含む)“hello”

              メモリレイアウト

              #![allow(unused)]
              fn main() {
              let s = String::from("hello world");
              let hello = &s[0..5];
              
              String (s)                   ヒープ
              ┌─────────────┐            ┌───────────────────────┐
              │ ptr ─────────────────────→│ h e l l o   w o r l d │
              │ len: 11     │            └───────────────────────┘
              │ cap: 11     │                ↑
              └─────────────┘                │ ptr
                                          ┌──┴────────┐
              &str (hello)               │ len: 5    │
                                         └───────────┘
              }

              &strは以下を持ちます:

              • ポインタ(データの開始位置)
              • 長さ

              String と &str の関係

              #![allow(unused)]
              fn main() {
              let s: String = String::from("hello");  // 所有権あり
              let slice: &str = &s;                   // Stringへのスライス
              let literal: &str = "hello";            // 文字列リテラル('static)
              }

              使い分け

              特徴用途
              String所有権あり、変更可能文字列を所有・変更したいとき
              &str参照、読み取り専用文字列を読むだけのとき

              関数の引数は&strを推奨

              #![allow(unused)]
              fn main() {
              // これより...
              fn greet(name: &String) { ... }
              
              // こちらが良い
              fn greet(name: &str) { ... }
              }

              &strを受け取る関数は、Stringからも文字列リテラルからも呼び出せます:

              fn greet(name: &str) {
                  println!("Hello, {}!", name);
              }
              
              fn main() {
                  let s = String::from("World");
              
                  greet(&s);        // Stringから(自動的に&strに変換)
                  greet("World");   // 文字列リテラルから
              }

              配列スライス

              配列やVecの一部も参照できます。

              fn main() {
                  let arr = [1, 2, 3, 4, 5];
                  let slice: &[i32] = &arr[1..4];  // [2, 3, 4]
              
                  println!("{:?}", slice);
              }

              Vecのスライス

              fn main() {
                  let v = vec![1, 2, 3, 4, 5];
                  let slice = &v[1..4];  // &[i32]型
              
                  println!("{:?}", slice);  // [2, 3, 4]
              }

              スライスを使った関数

              最初の単語を返す

              fn first_word(s: &str) -> &str {
                  let bytes = s.as_bytes();
              
                  for (i, &byte) in bytes.iter().enumerate() {
                      if byte == b' ' {
                          return &s[0..i];
                      }
                  }
              
                  &s[..]
              }
              
              fn main() {
                  let s = String::from("hello world");
                  let word = first_word(&s);
                  println!("最初の単語: {}", word);  // "hello"
              }

              配列の合計を計算

              fn sum(numbers: &[i32]) -> i32 {
                  let mut total = 0;
                  for n in numbers {
                      total += n;
                  }
                  total
              }
              
              fn main() {
                  let arr = [1, 2, 3, 4, 5];
                  let vec = vec![10, 20, 30];
              
                  println!("配列の合計: {}", sum(&arr));      // 15
                  println!("Vecの合計: {}", sum(&vec));       // 60
                  println!("一部の合計: {}", sum(&arr[1..4])); // 9 (2+3+4)
              }

              スライスの安全性

              スライスは参照なので、所有権ルールに従います。

              fn main() {
                  let mut s = String::from("hello world");
                  let word = first_word(&s);  // 不変借用
              
                  // s.clear();  // エラー!wordが有効な間は変更できない
              
                  println!("{}", word);
              }
              
              fn first_word(s: &str) -> &str {
                  &s[..5]
              }

              よくあるパターン

              パターン1: イテレーションしながらインデックスを使わない

              #![allow(unused)]
              fn main() {
              // 良くない(インデックスアクセス)
              fn process_bad(data: &[i32]) {
                  for i in 0..data.len() {
                      println!("{}", data[i]);
                  }
              }
              
              // 良い(イテレータ使用)
              fn process_good(data: &[i32]) {
                  for item in data {
                      println!("{}", item);
                  }
              }
              }

              パターン2: 範囲チェック

              #![allow(unused)]
              fn main() {
              fn safe_get(data: &[i32], index: usize) -> Option<&i32> {
                  data.get(index)  // 範囲外ならNone
              }
              }

              まとめ

              スライス型元のデータ用途
              &strString, 文字列リテラル文字列の一部を参照
              &[T]配列, Vec<T>コレクションの一部を参照
              特徴内容
              所有権なし(参照)
              サイズポインタ + 長さ
              安全性借用ルールに従う

              確認テスト

              Q1. &strStringの違いは?

              Q2. 関数の引数として文字列を受け取る場合、推奨される型は?

              Q3. 以下のコードの出力は?
              let s = String::from("hello world"); let part1 = &s[..5]; let part2 = &s[6..]; println!("{}-{}", part1, part2);

              Q4. let mut s = String::from("hello"); let slice = &s[..]; s.push_str(" world");がエラーになる理由は?

              Q5. スライス&[T]の特徴として正しいものは?


              Phase 2 完了!

              おめでとうございます!Rust最重要のPhase 2を完了しました。

              学んだこと:

              • スタックとヒープの違い
              • 所有権の3つのルール
              • 参照と借用(不変参照、可変参照)
              • ライフタイムの基礎
              • スライス(&str、&[T])

              **これらの概念はRustの核心です。**最初は難しく感じても、コードを書いていくうちに自然と理解できるようになります。

              次のPhase: Phase 3: 構造化プログラミング

              構造体(Struct)

              関連するデータをまとめる「構造体」について学びます。

              構造体とは

              構造体は複数の関連するデータをまとめた型です。

              #![allow(unused)]
              fn main() {
              struct User {
                  username: String,
                  email: String,
                  age: u32,
                  active: bool,
              }
              }

              構造体の定義と使用

              定義

              #![allow(unused)]
              fn main() {
              struct Point {
                  x: f64,
                  y: f64,
              }
              }

              インスタンスの作成

              fn main() {
                  let p = Point {
                      x: 3.0,
                      y: 4.0,
                  };
              
                  println!("座標: ({}, {})", p.x, p.y);
              }

              フィールドへのアクセス

              fn main() {
                  let mut user = User {
                      username: String::from("太郎"),
                      email: String::from("taro@example.com"),
                      age: 25,
                      active: true,
                  };
              
                  println!("名前: {}", user.username);
              
                  // mutなら変更可能
                  user.age = 26;
              }

              フィールド初期化省略記法

              変数名とフィールド名が同じなら省略できます。

              #![allow(unused)]
              fn main() {
              fn create_user(username: String, email: String) -> User {
                  User {
                      username,  // username: username の省略
                      email,     // email: email の省略
                      age: 0,
                      active: true,
                  }
              }
              }

              構造体更新構文

              既存のインスタンスから一部だけ変えた新しいインスタンスを作れます。

              fn main() {
                  let user1 = User {
                      username: String::from("太郎"),
                      email: String::from("taro@example.com"),
                      age: 25,
                      active: true,
                  };
              
                  let user2 = User {
                      email: String::from("jiro@example.com"),
                      ..user1  // 残りはuser1から
                  };
              
                  // 注意: user1.usernameはムーブされたので使えない
                  // println!("{}", user1.username);  // エラー!
                  println!("{}", user1.age);  // OK(Copyトレイト)
              }

              タプル構造体

              フィールド名のない構造体です。

              struct Color(u8, u8, u8);
              struct Point3D(f64, f64, f64);
              
              fn main() {
                  let black = Color(0, 0, 0);
                  let origin = Point3D(0.0, 0.0, 0.0);
              
                  println!("R: {}", black.0);
                  println!("x: {}", origin.0);
              }

              ユニット様構造体

              フィールドを持たない構造体です。トレイト実装に使います。

              struct AlwaysEqual;
              
              fn main() {
                  let _subject = AlwaysEqual;
              }

              メソッドの定義(impl)

              構造体に関連する関数をimplブロックで定義します。

              struct Rectangle {
                  width: u32,
                  height: u32,
              }
              
              impl Rectangle {
                  // メソッド(&selfを受け取る)
                  fn area(&self) -> u32 {
                      self.width * self.height
                  }
              
                  // 可変メソッド(&mut selfを受け取る)
                  fn double(&mut self) {
                      self.width *= 2;
                      self.height *= 2;
                  }
              
                  // 関連関数(selfを受け取らない)
                  fn square(size: u32) -> Rectangle {
                      Rectangle {
                          width: size,
                          height: size,
                      }
                  }
              }
              
              fn main() {
                  let mut rect = Rectangle {
                      width: 30,
                      height: 50,
                  };
              
                  println!("面積: {}", rect.area());
              
                  rect.double();
                  println!("2倍後の面積: {}", rect.area());
              
                  let sq = Rectangle::square(10);
                  println!("正方形の面積: {}", sq.area());
              }

              &self、&mut self、self の違い

              引数意味用途
              &self不変借用読み取りのみ
              &mut self可変借用値を変更
              self所有権を取得インスタンスを消費

              複数のimplブロック

              #![allow(unused)]
              fn main() {
              impl Rectangle {
                  fn area(&self) -> u32 {
                      self.width * self.height
                  }
              }
              
              impl Rectangle {
                  fn can_hold(&self, other: &Rectangle) -> bool {
                      self.width > other.width && self.height > other.height
                  }
              }
              }

              デバッグ出力

              #[derive(Debug)]を使うと、構造体を簡単に表示できます。

              #[derive(Debug)]
              struct Rectangle {
                  width: u32,
                  height: u32,
              }
              
              fn main() {
                  let rect = Rectangle {
                      width: 30,
                      height: 50,
                  };
              
                  println!("{:?}", rect);   // Rectangle { width: 30, height: 50 }
                  println!("{:#?}", rect);  // 整形表示
              }

              実践例: ユーザー管理

              #[derive(Debug)]
              struct User {
                  id: u32,
                  username: String,
                  email: String,
              }
              
              impl User {
                  fn new(id: u32, username: String, email: String) -> User {
                      User { id, username, email }
                  }
              
                  fn display(&self) {
                      println!("ID: {}, 名前: {}", self.id, self.username);
                  }
              
                  fn update_email(&mut self, new_email: String) {
                      self.email = new_email;
                  }
              }
              
              fn main() {
                  let mut user = User::new(1, String::from("太郎"), String::from("taro@example.com"));
                  user.display();
              
                  user.update_email(String::from("newtaro@example.com"));
                  println!("新しいメール: {}", user.email);
              }

              まとめ

              概念説明
              構造体関連データをまとめる
              implメソッドを定義
              &self読み取りメソッド
              &mut self変更メソッド
              関連関数Self::func()で呼ぶ
              #[derive(Debug)]デバッグ出力を有効化

              確認テスト

              Q1. メソッド定義で&selfを使う場合、何ができる?

              Q2. Rectangle::square(10)のような呼び出しは何と呼ばれる?

              Q3. 以下のコードの出力は?
              impl Counter { fn increment(&mut self) { self.value += 1; } } let mut c = Counter { value: 0 }; c.increment(); c.increment(); println!("{}", c.value);

              Q4. 以下のコードがコンパイルエラーになる理由は?
              impl Point { fn move_by(&self, dx: f64, dy: f64) { self.x += dx; self.y += dy; } }

              Q5. 構造体に#[derive(Debug)]を付けると何ができる?


              次のドキュメント: 02_enums_pattern.md

              列挙型とパターンマッチング

              複数の選択肢を表す「列挙型」と、それを扱う「パターンマッチング」を学びます。

              列挙型(Enum)とは

              列挙型は複数の選択肢のうち1つを表す型です。

              enum Direction {
                  North,
                  South,
                  East,
                  West,
              }
              
              fn main() {
                  let dir = Direction::North;
              }

              列挙型の定義と使用

              基本的な列挙型

              enum Status {
                  Pending,
                  InProgress,
                  Completed,
                  Cancelled,
              }
              
              fn main() {
                  let task_status = Status::InProgress;
              }

              データを持つ列挙型

              各バリアントがデータを持てます。

              enum Message {
                  Quit,                       // データなし
                  Move { x: i32, y: i32 },    // 構造体風
                  Write(String),              // 単一の値
                  ChangeColor(u8, u8, u8),    // タプル風
              }
              
              fn main() {
                  let msg1 = Message::Quit;
                  let msg2 = Message::Move { x: 10, y: 20 };
                  let msg3 = Message::Write(String::from("Hello"));
                  let msg4 = Message::ChangeColor(255, 0, 0);
              }

              match式

              matchは列挙型のすべてのバリアントを処理します。

              #![allow(unused)]
              fn main() {
              enum Coin {
                  Penny,
                  Nickel,
                  Dime,
                  Quarter,
              }
              
              fn value_in_cents(coin: Coin) -> u32 {
                  match coin {
                      Coin::Penny => 1,
                      Coin::Nickel => 5,
                      Coin::Dime => 10,
                      Coin::Quarter => 25,
                  }
              }
              }

              matchは網羅的

              すべてのバリアントを扱わないとエラーになります。

              #![allow(unused)]
              fn main() {
              fn value_in_cents(coin: Coin) -> u32 {
                  match coin {
                      Coin::Penny => 1,
                      // エラー!他のバリアントが抜けている
                  }
              }
              }

              データの取り出し

              #![allow(unused)]
              fn main() {
              enum Message {
                  Quit,
                  Move { x: i32, y: i32 },
                  Write(String),
              }
              
              fn process(msg: Message) {
                  match msg {
                      Message::Quit => {
                          println!("終了します");
                      }
                      Message::Move { x, y } => {
                          println!("移動: ({}, {})", x, y);
                      }
                      Message::Write(text) => {
                          println!("メッセージ: {}", text);
                      }
                  }
              }
              }

              _ プレースホルダ

              残りすべてにマッチします。

              #![allow(unused)]
              fn main() {
              fn describe_number(n: i32) {
                  match n {
                      1 => println!("一"),
                      2 => println!("二"),
                      3 => println!("三"),
                      _ => println!("その他"),  // 1, 2, 3以外すべて
                  }
              }
              }

              Option型(復習と深掘り)

              Optionは「値があるかないか」を表す標準の列挙型です。

              #![allow(unused)]
              fn main() {
              enum Option<T> {
                  Some(T),
                  None,
              }
              }

              Optionの使用

              fn find_user(id: u32) -> Option<String> {
                  if id == 1 {
                      Some(String::from("太郎"))
                  } else {
                      None
                  }
              }
              
              fn main() {
                  match find_user(1) {
                      Some(name) => println!("見つかりました: {}", name),
                      None => println!("見つかりませんでした"),
                  }
              }

              Result型(復習と深掘り)

              Resultは「成功か失敗か」を表す標準の列挙型です。

              #![allow(unused)]
              fn main() {
              enum Result<T, E> {
                  Ok(T),
                  Err(E),
              }
              }

              Resultの使用

              fn divide(a: f64, b: f64) -> Result<f64, String> {
                  if b == 0.0 {
                      Err(String::from("ゼロ除算エラー"))
                  } else {
                      Ok(a / b)
                  }
              }
              
              fn main() {
                  match divide(10.0, 2.0) {
                      Ok(result) => println!("結果: {}", result),
                      Err(e) => println!("エラー: {}", e),
                  }
              }

              if let 式

              特定のパターンだけ処理したい場合に便利です。

              fn main() {
                  let some_value = Some(5);
              
                  // matchを使う場合
                  match some_value {
                      Some(n) => println!("値: {}", n),
                      None => (),  // 何もしない
                  }
              
                  // if letを使う場合(簡潔)
                  if let Some(n) = some_value {
                      println!("値: {}", n);
                  }
              }

              if let else

              fn main() {
                  let coin = Coin::Quarter;
              
                  if let Coin::Quarter = coin {
                      println!("25セント!");
                  } else {
                      println!("25セントではない");
                  }
              }

              while let 式

              パターンがマッチする間ループします。

              fn main() {
                  let mut stack = vec![1, 2, 3];
              
                  while let Some(top) = stack.pop() {
                      println!("{}", top);
                  }
              }
              // 出力: 3, 2, 1

              列挙型のメソッド

              列挙型にもメソッドを定義できます。

              enum Status {
                  Pending,
                  InProgress,
                  Completed,
              }
              
              impl Status {
                  fn description(&self) -> &str {
                      match self {
                          Status::Pending => "未着手",
                          Status::InProgress => "進行中",
                          Status::Completed => "完了",
                      }
                  }
              
                  fn is_done(&self) -> bool {
                      matches!(self, Status::Completed)
                  }
              }
              
              fn main() {
                  let status = Status::InProgress;
                  println!("{}", status.description());
                  println!("完了?: {}", status.is_done());
              }

              実践例: 状態管理

              #![allow(unused)]
              fn main() {
              #[derive(Debug)]
              enum TaskState {
                  Todo,
                  InProgress { progress: u8 },
                  Done { completed_at: String },
              }
              
              struct Task {
                  name: String,
                  state: TaskState,
              }
              
              impl Task {
                  fn new(name: String) -> Task {
                      Task {
                          name,
                          state: TaskState::Todo,
                      }
                  }
              
                  fn start(&mut self) {
                      self.state = TaskState::InProgress { progress: 0 };
                  }
              
                  fn update_progress(&mut self, progress: u8) {
                      if let TaskState::InProgress { progress: ref mut p } = self.state {
                          *p = progress;
                      }
                  }
              
                  fn complete(&mut self, completed_at: String) {
                      self.state = TaskState::Done { completed_at };
                  }
              }
              }

              まとめ

              概念説明
              列挙型複数の選択肢のうち1つを表す
              バリアント列挙型の各選択肢
              match網羅的なパターンマッチング
              if let特定パターンのみ処理
              Option値の有無を表す
              Result成功/失敗を表す

              確認テスト

              Q1. match式の特徴として正しいのは?

              Q2. if letを使うべき場面は?

              Q3. 以下のコードの出力は?
              let light = Light::Yellow; let action = match light { Light::Red => "止まれ", Light::Yellow => "注意", Light::Green => "進め" }; println!("{}", action);

              Q4. 以下のコードがコンパイルエラーになる理由は?
              enum Color { Red, Green, Blue } fn describe(c: Color) -> &str { match c { Color::Red => "赤", Color::Green => "緑" } }

              Q5. Option<T>Noneは何を表す?


              次のドキュメント: 03_traits.md

              トレイト(Trait)

              共通の振る舞いを定義する「トレイト」について学びます。

              トレイトとは

              トレイトは型が持つべき振る舞い(メソッド)を定義します。他の言語の「インターフェース」に似ています。

              #![allow(unused)]
              fn main() {
              trait Summary {
                  fn summarize(&self) -> String;
              }
              }

              トレイトの定義と実装

              トレイトを定義

              #![allow(unused)]
              fn main() {
              trait Greet {
                  fn greet(&self) -> String;
              }
              }

              型にトレイトを実装

              struct Person {
                  name: String,
              }
              
              impl Greet for Person {
                  fn greet(&self) -> String {
                      format!("こんにちは、{}です!", self.name)
                  }
              }
              
              struct Robot {
                  id: u32,
              }
              
              impl Greet for Robot {
                  fn greet(&self) -> String {
                      format!("ピポパポ、ロボット{}号です", self.id)
                  }
              }
              
              fn main() {
                  let person = Person { name: String::from("太郎") };
                  let robot = Robot { id: 42 };
              
                  println!("{}", person.greet());
                  println!("{}", robot.greet());
              }

              デフォルト実装

              トレイトにデフォルトの実装を提供できます。

              #![allow(unused)]
              fn main() {
              trait Summary {
                  fn summarize(&self) -> String {
                      String::from("(詳細なし)")
                  }
              }
              
              struct Article {
                  title: String,
                  content: String,
              }
              
              // デフォルト実装をそのまま使う
              impl Summary for Article {}
              
              struct Tweet {
                  username: String,
                  text: String,
              }
              
              // カスタム実装で上書き
              impl Summary for Tweet {
                  fn summarize(&self) -> String {
                      format!("@{}: {}", self.username, self.text)
                  }
              }
              }

              トレイト境界(引数で使う)

              「このトレイトを実装した型」を引数に取れます。

              #![allow(unused)]
              fn main() {
              // impl Trait 構文(簡潔)
              fn notify(item: &impl Summary) {
                  println!("速報!{}", item.summarize());
              }
              
              // トレイト境界構文(より明示的)
              fn notify2<T: Summary>(item: &T) {
                  println!("速報!{}", item.summarize());
              }
              }

              複数のトレイト境界

              #![allow(unused)]
              fn main() {
              fn notify(item: &(impl Summary + Display)) {
                  // SummaryとDisplayの両方を実装した型のみ
              }
              
              // または
              fn notify<T: Summary + Display>(item: &T) {
                  // ...
              }
              }

              where句(複雑な場合)

              #![allow(unused)]
              fn main() {
              fn some_function<T, U>(t: &T, u: &U)
              where
                  T: Summary + Clone,
                  U: Clone + Debug,
              {
                  // ...
              }
              }

              トレイトを返す

              #![allow(unused)]
              fn main() {
              fn create_summarizable() -> impl Summary {
                  Tweet {
                      username: String::from("user"),
                      text: String::from("Hello!"),
                  }
              }
              }

              標準ライブラリの重要なトレイト

              Debug - デバッグ出力

              #[derive(Debug)]
              struct Point {
                  x: i32,
                  y: i32,
              }
              
              fn main() {
                  let p = Point { x: 1, y: 2 };
                  println!("{:?}", p);
              }

              Clone - 明示的なコピー

              #[derive(Clone)]
              struct Data {
                  value: String,
              }
              
              fn main() {
                  let d1 = Data { value: String::from("hello") };
                  let d2 = d1.clone();
              }

              PartialEq - 等価比較

              #[derive(PartialEq)]
              struct Point {
                  x: i32,
                  y: i32,
              }
              
              fn main() {
                  let p1 = Point { x: 1, y: 2 };
                  let p2 = Point { x: 1, y: 2 };
                  println!("{}", p1 == p2);  // true
              }

              Display - ユーザー向け表示

              use std::fmt;
              
              struct Point {
                  x: i32,
                  y: i32,
              }
              
              impl fmt::Display for Point {
                  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                      write!(f, "({}, {})", self.x, self.y)
                  }
              }
              
              fn main() {
                  let p = Point { x: 1, y: 2 };
                  println!("{}", p);  // (1, 2)
              }

              Default - デフォルト値

              #[derive(Default)]
              struct Config {
                  debug: bool,
                  max_connections: u32,
              }
              
              fn main() {
                  let config = Config::default();
                  // debug: false, max_connections: 0
              }

              derive マクロ

              よく使うトレイトはderiveで自動実装できます。

              #![allow(unused)]
              fn main() {
              #[derive(Debug, Clone, PartialEq, Default)]
              struct User {
                  name: String,
                  age: u32,
              }
              }

              実践例: 動物の鳴き声

              trait Animal {
                  fn name(&self) -> &str;
                  fn speak(&self) -> String;
              
                  fn introduce(&self) -> String {
                      format!("私は{}です。{}", self.name(), self.speak())
                  }
              }
              
              struct Dog {
                  name: String,
              }
              
              impl Animal for Dog {
                  fn name(&self) -> &str {
                      &self.name
                  }
              
                  fn speak(&self) -> String {
                      String::from("ワンワン!")
                  }
              }
              
              struct Cat {
                  name: String,
              }
              
              impl Animal for Cat {
                  fn name(&self) -> &str {
                      &self.name
                  }
              
                  fn speak(&self) -> String {
                      String::from("ニャー")
                  }
              }
              
              fn main() {
                  let dog = Dog { name: String::from("ポチ") };
                  let cat = Cat { name: String::from("タマ") };
              
                  println!("{}", dog.introduce());
                  println!("{}", cat.introduce());
              }

              まとめ

              概念説明
              トレイト共通の振る舞いを定義
              impl Trait for Type型にトレイトを実装
              デフォルト実装トレイト内でデフォルトのメソッドを提供
              トレイト境界「このトレイトを実装した型」という制約
              derive標準トレイトの自動実装

              確認テスト

              Q1. トレイトの役割は?

              Q2. #[derive(Debug)]の効果は?

              Q3. 以下のコードで、Silent.speak()Loud.speak()の出力は?
              trait Speak { fn speak(&self) -> String { String::from("...") } } struct Silent; impl Speak for Silent {} struct Loud; impl Speak for Loud { fn speak(&self) -> String { String::from("HELLO!") } }

              Q4. fn show(item: Printable)がエラーになる理由は?

              Q5. トレイト境界T: Clone + Debugは何を意味する?


              次のドキュメント: 04_generics.md

              ジェネリクス

              型をパラメータ化する「ジェネリクス」について学びます。

              ジェネリクスとは

              ジェネリクスは型を抽象化し、同じコードを複数の型で使えるようにします。

              #![allow(unused)]
              fn main() {
              // i32用
              fn largest_i32(list: &[i32]) -> i32 { ... }
              
              // f64用
              fn largest_f64(list: &[f64]) -> f64 { ... }
              
              // ジェネリクスで1つに
              fn largest<T>(list: &[T]) -> T { ... }
              }

              関数のジェネリクス

              fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
                  let mut largest = list[0];
              
                  for &item in list {
                      if item > largest {
                          largest = item;
                      }
                  }
              
                  largest
              }
              
              fn main() {
                  let numbers = vec![34, 50, 25, 100, 65];
                  println!("最大: {}", largest(&numbers));
              
                  let chars = vec!['y', 'm', 'a', 'q'];
                  println!("最大: {}", largest(&chars));
              }

              型パラメータの慣習

              • T - Type(一般的な型)
              • E - Error(エラー型)
              • K, V - Key, Value(マップ用)
              • R - Result(結果型)

              構造体のジェネリクス

              struct Point<T> {
                  x: T,
                  y: T,
              }
              
              fn main() {
                  let integer_point = Point { x: 5, y: 10 };
                  let float_point = Point { x: 1.0, y: 4.0 };
              }

              複数の型パラメータ

              struct Point<T, U> {
                  x: T,
                  y: U,
              }
              
              fn main() {
                  let mixed = Point { x: 5, y: 4.0 };
              }

              ジェネリクス構造体のメソッド

              #![allow(unused)]
              fn main() {
              struct Point<T> {
                  x: T,
                  y: T,
              }
              
              impl<T> Point<T> {
                  fn x(&self) -> &T {
                      &self.x
                  }
              }
              
              // 特定の型にのみ実装
              impl Point<f64> {
                  fn distance_from_origin(&self) -> f64 {
                      (self.x.powi(2) + self.y.powi(2)).sqrt()
                  }
              }
              }

              列挙型のジェネリクス

              標準ライブラリのOptionResultがまさにこれです。

              #![allow(unused)]
              fn main() {
              enum Option<T> {
                  Some(T),
                  None,
              }
              
              enum Result<T, E> {
                  Ok(T),
                  Err(E),
              }
              }

              自作のジェネリクス列挙型

              enum Response<T> {
                  Success(T),
                  Loading,
                  Error(String),
              }
              
              fn main() {
                  let user_response: Response<String> = Response::Success(String::from("太郎"));
                  let count_response: Response<i32> = Response::Success(42);
              }

              トレイト境界

              ジェネリクスに「このトレイトを実装している型のみ」という制約をつけます。

              #![allow(unused)]
              fn main() {
              use std::fmt::Display;
              
              fn print_info<T: Display>(item: T) {
                  println!("情報: {}", item);
              }
              }

              複数のトレイト境界

              #![allow(unused)]
              fn main() {
              fn compare_and_display<T: PartialOrd + Display>(a: T, b: T) {
                  if a > b {
                      println!("{} > {}", a, b);
                  } else {
                      println!("{} <= {}", a, b);
                  }
              }
              }

              where句

              境界が複雑な場合に読みやすくなります。

              #![allow(unused)]
              fn main() {
              fn some_function<T, U>(t: T, u: U) -> i32
              where
                  T: Display + Clone,
                  U: Clone + Debug,
              {
                  // ...
              }
              }

              実践例: ジェネリックなペア

              #[derive(Debug)]
              struct Pair<T> {
                  first: T,
                  second: T,
              }
              
              impl<T> Pair<T> {
                  fn new(first: T, second: T) -> Pair<T> {
                      Pair { first, second }
                  }
              
                  fn swap(&mut self) {
                      std::mem::swap(&mut self.first, &mut self.second);
                  }
              }
              
              impl<T: PartialOrd + Display> Pair<T> {
                  fn cmp_display(&self) {
                      if self.first > self.second {
                          println!("最大は {}", self.first);
                      } else {
                          println!("最大は {}", self.second);
                      }
                  }
              }
              
              fn main() {
                  let mut pair = Pair::new(5, 10);
                  pair.cmp_display();
                  pair.swap();
                  println!("{:?}", pair);
              }

              ジェネリクスとパフォーマンス

              Rustのジェネリクスは単相化(Monomorphization)されます。

              #![allow(unused)]
              fn main() {
              // このコードは
              fn largest<T: PartialOrd>(a: T, b: T) -> T { ... }
              largest(5, 10);
              largest(1.0, 2.0);
              
              // コンパイル時にこうなる
              fn largest_i32(a: i32, b: i32) -> i32 { ... }
              fn largest_f64(a: f64, b: f64) -> f64 { ... }
              }

              つまり、実行時のコストはゼロです!

              標準ライブラリのジェネリクス例

              #![allow(unused)]
              fn main() {
              // Vec<T>
              let v: Vec<i32> = vec![1, 2, 3];
              let v: Vec<String> = vec![String::from("a")];
              
              // HashMap<K, V>
              use std::collections::HashMap;
              let mut map: HashMap<String, i32> = HashMap::new();
              
              // Option<T>
              let some: Option<i32> = Some(5);
              
              // Result<T, E>
              let result: Result<i32, String> = Ok(42);
              }

              まとめ

              概念説明
              ジェネリクス型をパラメータ化
              <T>型パラメータ
              トレイト境界T: Traitで制約を追加
              where複雑な境界を読みやすく
              単相化コンパイル時に具体型に展開(ゼロコスト)

              確認テスト

              Q1. ジェネリクスの主な目的は?

              Q2. fn foo<T: Clone + Debug>(x: T)のトレイト境界が意味するのは?

              Q3. 以下のコードの出力は?
              struct Container<T> { value: T } impl<T> Container<T> { fn new(value: T) -> Self { Container { value } } fn get(&self) -> &T { &self.value } } let c = Container::new(42); println!("{}", c.get());

              Q4. fn print_largest<T>(a: T, b: T)a > bを使うために必要なトレイト境界は?

              Q5. Rustのジェネリクスの「単相化(Monomorphization)」とは?


              次のドキュメント: 05_modules.md

              モジュールシステム

              コードを整理する「モジュール」について学びます。

              モジュールとは

              モジュールはコードを論理的に分割する仕組みです。

              my_project/
              ├── src/
              │   ├── main.rs      # エントリーポイント
              │   ├── lib.rs       # ライブラリクレートのルート
              │   ├── utils.rs     # utilsモジュール
              │   └── models/
              │       ├── mod.rs   # modelsモジュールのルート
              │       └── user.rs  # models::userサブモジュール
              

              基本的なモジュール定義

              同一ファイル内

              mod greetings {
                  pub fn hello() {
                      println!("Hello!");
                  }
              
                  fn private_function() {
                      println!("This is private");
                  }
              }
              
              fn main() {
                  greetings::hello();
                  // greetings::private_function();  // エラー!プライベート
              }

              別ファイルに分割

              src/main.rs

              mod utils;  // src/utils.rs を読み込む
              
              fn main() {
                  utils::greet();
              }

              src/utils.rs

              #![allow(unused)]
              fn main() {
              pub fn greet() {
                  println!("Hello from utils!");
              }
              }

              pub(公開)キーワード

              デフォルトはプライベート。pubで公開します。

              mod outer {
                  pub mod inner {
                      pub fn public_function() {
                          println!("公開関数");
                      }
              
                      fn private_function() {
                          println!("非公開関数");
                      }
                  }
              }
              
              fn main() {
                  outer::inner::public_function();
              }

              構造体フィールドの公開

              mod models {
                  pub struct User {
                      pub name: String,    // 公開
                      email: String,       // 非公開
                  }
              
                  impl User {
                      pub fn new(name: String, email: String) -> User {
                          User { name, email }
                      }
              
                      pub fn email(&self) -> &str {
                          &self.email
                      }
                  }
              }
              
              fn main() {
                  let user = models::User::new(
                      String::from("太郎"),
                      String::from("taro@example.com"),
                  );
                  println!("名前: {}", user.name);
                  // println!("メール: {}", user.email);  // エラー!非公開
                  println!("メール: {}", user.email());   // メソッド経由でOK
              }

              use キーワード

              長いパスを短縮します。

              mod models {
                  pub mod user {
                      pub struct User {
                          pub name: String,
                      }
                  }
              }
              
              // useでパスを短縮
              use models::user::User;
              
              fn main() {
                  let u = User { name: String::from("太郎") };
                  // models::user::User と書かなくてよい
              }

              複数のインポート

              #![allow(unused)]
              fn main() {
              use std::collections::{HashMap, HashSet};
              use std::io::{self, Read, Write};
              }

              エイリアス(as)

              use std::collections::HashMap as Map;
              
              fn main() {
                  let mut m: Map<String, i32> = Map::new();
              }

              再エクスポート(pub use)

              #![allow(unused)]
              fn main() {
              mod models {
                  pub mod user {
                      pub struct User { pub name: String }
                  }
              }
              
              // 外部からmodels::Userでアクセス可能にする
              pub use models::user::User;
              }

              ディレクトリ構造

              方法1: mod.rs を使う

              src/
              ├── main.rs
              └── models/
                  ├── mod.rs     # モジュールのルート
                  └── user.rs
              

              src/main.rs

              #![allow(unused)]
              fn main() {
              mod models;
              use models::User;
              }

              src/models/mod.rs

              #![allow(unused)]
              fn main() {
              mod user;
              pub use user::User;
              }

              src/models/user.rs

              #![allow(unused)]
              fn main() {
              pub struct User {
                  pub name: String,
              }
              }

              方法2: ファイル名でモジュール(Rust 2018以降)

              src/
              ├── main.rs
              ├── models.rs      # mod models の定義
              └── models/
                  └── user.rs    # mod models::user の定義
              

              src/models.rs

              #![allow(unused)]
              fn main() {
              pub mod user;
              pub use user::User;
              }

              クレート(Crate)

              クレートはRustのコンパイル単位です。

              • バイナリクレート: 実行可能ファイル(main.rs
              • ライブラリクレート: 他のコードから使うライブラリ(lib.rs

              外部クレートの使用

              Cargo.toml

              [dependencies]
              rand = "0.8"
              serde = { version = "1.0", features = ["derive"] }
              

              src/main.rs

              use rand::Rng;
              
              fn main() {
                  let n = rand::thread_rng().gen_range(1..100);
                  println!("{}", n);
              }

              実践的なプロジェクト構造

              my_app/
              ├── Cargo.toml
              └── src/
                  ├── main.rs          # エントリーポイント
                  ├── lib.rs           # ライブラリ(オプション)
                  ├── config.rs        # 設定モジュール
                  ├── error.rs         # エラー定義
                  ├── models/
                  │   ├── mod.rs
                  │   ├── user.rs
                  │   └── post.rs
                  └── handlers/
                      ├── mod.rs
                      ├── auth.rs
                      └── api.rs
              

              まとめ

              概念説明
              modモジュールを定義/インポート
              pub公開する(デフォルトは非公開)
              useパスを短縮
              pub use再エクスポート
              クレートコンパイル単位(binary/library)

              確認テスト

              Q1. Rustのアイテム(関数、構造体など)のデフォルトの可視性は?

              Q2. use std::collections::{HashMap, HashSet};は何をしている?

              Q3. 以下のコードがコンパイルエラーになる理由は?
              mod secret { fn hidden() { println!("Hidden!"); } } fn main() { secret::hidden(); }

              Q4. pub useの役割は?

              Q5. Rustにおける「クレート(Crate)」とは?


              次のドキュメント: 06_error_handling_adv.md

              エラーハンドリング応用

              実践的なエラー処理のパターンを学びます。

              ? 演算子

              エラーを呼び出し元に伝播させます。

              #![allow(unused)]
              fn main() {
              use std::fs::File;
              use std::io::{self, Read};
              
              // ? を使わない場合
              fn read_file_verbose(path: &str) -> Result<String, io::Error> {
                  let file = match File::open(path) {
                      Ok(f) => f,
                      Err(e) => return Err(e),
                  };
              
                  let mut contents = String::new();
                  match file.read_to_string(&mut contents) {
                      Ok(_) => Ok(contents),
                      Err(e) => Err(e),
                  }
              }
              
              // ? を使う場合(簡潔!)
              fn read_file(path: &str) -> Result<String, io::Error> {
                  let mut file = File::open(path)?;
                  let mut contents = String::new();
                  file.read_to_string(&mut contents)?;
                  Ok(contents)
              }
              }

              ? の動作

              #![allow(unused)]
              fn main() {
              let value = some_result?;
              
              // は以下と同等
              let value = match some_result {
                  Ok(v) => v,
                  Err(e) => return Err(e.into()),
              };
              }

              チェーン

              #![allow(unused)]
              fn main() {
              fn read_file(path: &str) -> Result<String, io::Error> {
                  let mut contents = String::new();
                  File::open(path)?.read_to_string(&mut contents)?;
                  Ok(contents)
              }
              
              // さらに簡潔に
              fn read_file_short(path: &str) -> Result<String, io::Error> {
                  std::fs::read_to_string(path)
              }
              }

              カスタムエラー型

              基本的なカスタムエラー

              #![allow(unused)]
              fn main() {
              #[derive(Debug)]
              enum AppError {
                  NotFound(String),
                  InvalidInput(String),
                  IoError(std::io::Error),
              }
              
              impl std::fmt::Display for AppError {
                  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                      match self {
                          AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
                          AppError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
                          AppError::IoError(e) => write!(f, "IO error: {}", e),
                      }
                  }
              }
              
              impl std::error::Error for AppError {}
              }

              From トレイトで変換を自動化

              #![allow(unused)]
              fn main() {
              impl From<std::io::Error> for AppError {
                  fn from(error: std::io::Error) -> Self {
                      AppError::IoError(error)
                  }
              }
              
              // これで ? が自動変換してくれる
              fn do_something() -> Result<(), AppError> {
                  let _file = std::fs::File::open("file.txt")?;  // io::Error -> AppError
                  Ok(())
              }
              }

              thiserror クレート(推奨)

              カスタムエラーを簡単に定義できます。

              Cargo.toml

              [dependencies]
              thiserror = "1.0"
              
              #![allow(unused)]
              fn main() {
              use thiserror::Error;
              
              #[derive(Error, Debug)]
              enum AppError {
                  #[error("User not found: {0}")]
                  NotFound(String),
              
                  #[error("Invalid input: {0}")]
                  InvalidInput(String),
              
                  #[error("Database error")]
                  DatabaseError(#[from] sqlx::Error),
              
                  #[error("IO error")]
                  IoError(#[from] std::io::Error),
              }
              }

              anyhow クレート(アプリケーション向け)

              エラー型を気にせず簡単にエラー処理できます。

              Cargo.toml

              [dependencies]
              anyhow = "1.0"
              
              use anyhow::{Result, Context};
              
              fn read_config() -> Result<String> {
                  let content = std::fs::read_to_string("config.toml")
                      .context("設定ファイルの読み込みに失敗")?;
                  Ok(content)
              }
              
              fn main() -> Result<()> {
                  let config = read_config()?;
                  println!("{}", config);
                  Ok(())
              }

              エラー処理のパターン

              パターン1: 早期リターン

              #![allow(unused)]
              fn main() {
              fn process_user(id: u32) -> Result<User, AppError> {
                  if id == 0 {
                      return Err(AppError::InvalidInput("ID cannot be 0".into()));
                  }
              
                  let user = find_user(id)?;
              
                  if !user.is_active {
                      return Err(AppError::NotFound("User is inactive".into()));
                  }
              
                  Ok(user)
              }
              }

              パターン2: map_err で変換

              #![allow(unused)]
              fn main() {
              fn read_config() -> Result<Config, AppError> {
                  let content = std::fs::read_to_string("config.toml")
                      .map_err(|e| AppError::IoError(e))?;
              
                  toml::from_str(&content)
                      .map_err(|e| AppError::InvalidInput(e.to_string()))
              }
              }

              パターン3: ok_or / ok_or_else

              #![allow(unused)]
              fn main() {
              fn get_user(id: u32) -> Result<User, AppError> {
                  users.get(&id)
                      .cloned()
                      .ok_or_else(|| AppError::NotFound(format!("User {} not found", id)))
              }
              }

              パターン4: and_then でチェーン

              #![allow(unused)]
              fn main() {
              fn process() -> Result<String, AppError> {
                  read_file("input.txt")
                      .and_then(|content| parse_content(&content))
                      .and_then(|data| transform_data(data))
                      .and_then(|result| save_result(result))
              }
              }

              main関数でのエラー処理

              use std::process;
              
              fn main() {
                  if let Err(e) = run() {
                      eprintln!("エラー: {}", e);
                      process::exit(1);
                  }
              }
              
              fn run() -> Result<(), Box<dyn std::error::Error>> {
                  let config = read_config()?;
                  let data = process_data(&config)?;
                  save_output(&data)?;
                  Ok(())
              }

              パニックとの使い分け

              状況使うもの
              回復可能なエラーResult<T, E>
              プログラムのバグpanic!
              テストunwrap(), expect()
              プロトタイプunwrap()
              本番コード適切なエラー処理
              #![allow(unused)]
              fn main() {
              // panic! を使う場面(プログラムのバグ)
              fn get_element(index: usize) -> &str {
                  let items = ["a", "b", "c"];
                  items.get(index).expect("Index should be valid")
              }
              
              // Result を使う場面(回復可能なエラー)
              fn find_user(id: u32) -> Result<User, AppError> {
                  // ...
              }
              }

              まとめ

              概念説明
              ?エラーを伝播
              カスタムエラーアプリ固有のエラー型
              thiserrorエラー型定義を簡単に
              anyhow柔軟なエラー処理
              map_errエラー型を変換
              contextエラーに文脈を追加

              確認テスト

              Q1. ?演算子の動作は?

              Q2. thiserroranyhowの使い分けは?

              Q3. map_errの役割は?

              Q4. 以下のコードを?で簡潔にした結果は?
              let content = match std::fs::read_to_string(path) { Ok(c) => c, Err(e) => return Err(e) };

              Q5. panic!を使うべき場面は?


              Phase 3 完了!

              おめでとうございます!Phase 3を完了しました。

              学んだこと:

              • 構造体(struct、impl)
              • 列挙型とパターンマッチング
              • トレイト
              • ジェネリクス
              • モジュールシステム
              • エラーハンドリング応用

              次のPhase: Phase 4: エコシステムと実践

              Cargo活用

              Rustのビルドツール「Cargo」の高度な機能を学びます。

              Cargo.tomlの詳細

              基本構造

              [package]
              name = "my_app"
              version = "0.1.0"
              edition = "2021"
              authors = ["Your Name <you@example.com>"]
              description = "My awesome application"
              license = "MIT"
              
              [dependencies]
              serde = "1.0"
              
              [dev-dependencies]
              criterion = "0.4"
              
              [build-dependencies]
              cc = "1.0"
              

              バージョン指定

              [dependencies]
              # 完全一致
              exact = "=1.0.0"
              
              # キャレット(デフォルト): 互換性のある最新
              caret = "1.0"      # >=1.0.0, <2.0.0
              caret2 = "1.0.0"   # >=1.0.0, <2.0.0
              
              # チルダ: マイナーバージョンまで固定
              tilde = "~1.0"     # >=1.0.0, <1.1.0
              
              # ワイルドカード
              wildcard = "1.*"   # >=1.0.0, <2.0.0
              
              # 範囲
              range = ">=1.0, <2.0"
              

              Features(機能フラグ)

              依存クレートのfeatures

              [dependencies]
              serde = { version = "1.0", features = ["derive"] }
              tokio = { version = "1", features = ["full"] }
              
              # または一部のみ
              tokio = { version = "1", features = ["rt", "net", "io-util"] }
              

              自分のクレートにfeatures定義

              [features]
              default = ["json"]
              json = ["dep:serde_json"]
              yaml = ["dep:serde_yaml"]
              full = ["json", "yaml"]
              
              [dependencies]
              serde_json = { version = "1.0", optional = true }
              serde_yaml = { version = "0.9", optional = true }
              
              #![allow(unused)]
              fn main() {
              #[cfg(feature = "json")]
              pub fn parse_json(s: &str) -> Result<Value, Error> {
                  serde_json::from_str(s)
              }
              }

              ビルドプロファイル

              dev(開発用)

              [profile.dev]
              opt-level = 0      # 最適化なし
              debug = true       # デバッグ情報あり
              

              release(本番用)

              [profile.release]
              opt-level = 3      # 最大最適化
              debug = false      # デバッグ情報なし
              lto = true         # リンク時最適化
              

              カスタムプロファイル

              [profile.bench]
              opt-level = 3
              debug = true
              
              [profile.profiling]
              inherits = "release"
              debug = true
              

              ワークスペース

              複数のクレートをまとめて管理します。

              my_workspace/
              ├── Cargo.toml        # ワークスペースルート
              ├── app/
              │   ├── Cargo.toml
              │   └── src/
              ├── lib_core/
              │   ├── Cargo.toml
              │   └── src/
              └── lib_utils/
                  ├── Cargo.toml
                  └── src/
              

              ルートCargo.toml

              [workspace]
              members = ["app", "lib_core", "lib_utils"]
              

              app/Cargo.toml

              [package]
              name = "app"
              version = "0.1.0"
              
              [dependencies]
              lib_core = { path = "../lib_core" }
              lib_utils = { path = "../lib_utils" }
              

              便利なCargoコマンド

              コマンド説明
              cargo buildビルド
              cargo build --releaseリリースビルド
              cargo runビルド+実行
              cargo check型チェックのみ(高速)
              cargo testテスト実行
              cargo doc --openドキュメント生成&表示
              cargo fmtコード整形
              cargo clippy静的解析
              cargo update依存関係更新
              cargo tree依存関係ツリー表示

              Cargo.lock

              依存関係の正確なバージョンを記録します。

              • ライブラリ: .gitignoreに入れる(利用者が決める)
              • アプリケーション: コミットする(再現性のため)

              環境変数

              // ビルド時の情報
              const VERSION: &str = env!("CARGO_PKG_VERSION");
              const NAME: &str = env!("CARGO_PKG_NAME");
              
              fn main() {
                  println!("{} v{}", NAME, VERSION);
              }

              まとめ

              機能用途
              featuresオプション機能の切り替え
              profiles最適化レベルの設定
              workspace複数クレートの管理
              cargo check高速な型チェック
              cargo clippy静的解析

              確認テスト

              Q1. `cargo check`と`cargo build`の違いは?

              Q2. `[dependencies]`と`[dev-dependencies]`の違いは?

              Q3. 以下のCargo.tomlで`serde`のどのfeaturesが有効になる?
              serde = { version = "1.0", features = ["derive", "rc"] }

              Q4. 以下のCargo.tomlの問題は何?
              tokio = "1", features = ["full"]

              Q5. ワークスペースのルートCargo.tomlで正しい記述は?


              次のドキュメント: 02_crate_ecosystem.md

              クレートエコシステム

              Rustの豊富なライブラリ(クレート)を活用する方法を学びます。

              crates.io

              crates.io はRustの公式パッケージレジストリです。

              クレートを探す

              1. crates.ioで検索
              2. lib.rs で分類別に探す
              3. GitHubのAwesome Rustリストを参照

              良いクレートの選び方

              指標確認ポイント
              ダウンロード数多いほど信頼性が高い傾向
              最終更新活発にメンテナンスされているか
              ドキュメントdocs.rsで確認
              依存関係少ないほどシンプル
              ライセンスMIT/Apache 2.0が一般的

              主要クレート紹介

              シリアライゼーション: serde

              [dependencies]
              serde = { version = "1.0", features = ["derive"] }
              serde_json = "1.0"
              
              use serde::{Deserialize, Serialize};
              
              #[derive(Serialize, Deserialize, Debug)]
              struct User {
                  name: String,
                  age: u32,
              }
              
              fn main() {
                  let user = User {
                      name: String::from("太郎"),
                      age: 25,
                  };
              
                  // JSON化
                  let json = serde_json::to_string(&user).unwrap();
                  println!("{}", json);
              
                  // JSONからパース
                  let parsed: User = serde_json::from_str(&json).unwrap();
                  println!("{:?}", parsed);
              }

              HTTPクライアント: reqwest

              [dependencies]
              reqwest = { version = "0.11", features = ["json"] }
              tokio = { version = "1", features = ["full"] }
              
              use reqwest;
              
              #[tokio::main]
              async fn main() -> Result<(), reqwest::Error> {
                  let body = reqwest::get("https://httpbin.org/get")
                      .await?
                      .text()
                      .await?;
              
                  println!("{}", body);
                  Ok(())
              }

              非同期ランタイム: tokio

              [dependencies]
              tokio = { version = "1", features = ["full"] }
              
              use tokio::time::{sleep, Duration};
              
              #[tokio::main]
              async fn main() {
                  println!("開始");
                  sleep(Duration::from_secs(1)).await;
                  println!("1秒後");
              }

              CLI引数パース: clap

              [dependencies]
              clap = { version = "4", features = ["derive"] }
              
              use clap::Parser;
              
              #[derive(Parser, Debug)]
              #[command(name = "myapp")]
              #[command(about = "サンプルアプリケーション")]
              struct Args {
                  /// 名前
                  #[arg(short, long)]
                  name: String,
              
                  /// 回数
                  #[arg(short, long, default_value_t = 1)]
                  count: u8,
              }
              
              fn main() {
                  let args = Args::parse();
              
                  for _ in 0..args.count {
                      println!("Hello, {}!", args.name);
                  }
              }

              ログ: tracing

              [dependencies]
              tracing = "0.1"
              tracing-subscriber = "0.3"
              
              use tracing::{info, warn, error, debug};
              use tracing_subscriber;
              
              fn main() {
                  tracing_subscriber::fmt::init();
              
                  info!("アプリケーション開始");
                  debug!("デバッグ情報");
                  warn!("警告");
                  error!("エラー");
              }

              日時: chrono

              [dependencies]
              chrono = "0.4"
              
              use chrono::{Local, Utc};
              
              fn main() {
                  let now = Local::now();
                  println!("現在時刻: {}", now.format("%Y-%m-%d %H:%M:%S"));
              
                  let utc = Utc::now();
                  println!("UTC: {}", utc);
              }

              正規表現: regex

              [dependencies]
              regex = "1"
              
              use regex::Regex;
              
              fn main() {
                  let re = Regex::new(r"\d{3}-\d{4}").unwrap();
                  let text = "郵便番号: 123-4567";
              
                  if let Some(m) = re.find(text) {
                      println!("見つかりました: {}", m.as_str());
                  }
              }

              カテゴリ別おすすめクレート

              Web開発

              クレート用途
              axumWebフレームワーク
              actix-web高性能Webフレームワーク
              towerミドルウェア

              データベース

              クレート用途
              sqlx非同期SQL
              dieselORM
              sea-orm非同期ORM

              エラー処理

              クレート用途
              thiserrorカスタムエラー定義
              anyhow柔軟なエラー処理

              テスト

              クレート用途
              mockallモック
              proptestプロパティベーステスト

              まとめ

              カテゴリ推奨クレート
              シリアライズserde + serde_json
              HTTPreqwest
              非同期tokio
              CLIclap
              ログtracing
              日時chrono
              正規表現regex

              確認テスト

              Q1. serdeの主な用途は?

              Q2. tokioの役割は?

              Q3. `#[derive(Serialize, Deserialize)]`を構造体に付けると何ができる?

              Q4. `#[derive(Serialize, Deserialize)]`を使うために必要なCargo.tomlの設定は?

              Q5. chronoクレートで現在時刻を"2024-01-15 10:30:00"形式で表示するフォーマット文字列は?


              次のドキュメント: 03_testing.md

              テスト

              Rustの組み込みテスト機能を学びます。

              単体テスト

              基本的なテスト

              #![allow(unused)]
              fn main() {
              fn add(a: i32, b: i32) -> i32 {
                  a + b
              }
              
              #[cfg(test)]
              mod tests {
                  use super::*;
              
                  #[test]
                  fn test_add() {
                      assert_eq!(add(2, 3), 5);
                  }
              
                  #[test]
                  fn test_add_negative() {
                      assert_eq!(add(-1, 1), 0);
                  }
              }
              }

              実行:

              cargo test
              

              アサーションマクロ

              #![allow(unused)]
              fn main() {
              #[test]
              fn test_assertions() {
                  // 等値チェック
                  assert_eq!(1 + 1, 2);
              
                  // 不等値チェック
                  assert_ne!(1 + 1, 3);
              
                  // 条件チェック
                  assert!(true);
                  assert!(!false);
              
                  // カスタムメッセージ
                  assert_eq!(1 + 1, 2, "1 + 1 は 2 のはず");
              }
              }

              パニックのテスト

              #![allow(unused)]
              fn main() {
              fn divide(a: i32, b: i32) -> i32 {
                  if b == 0 {
                      panic!("ゼロ除算!");
                  }
                  a / b
              }
              
              #[test]
              #[should_panic]
              fn test_divide_by_zero() {
                  divide(10, 0);
              }
              
              #[test]
              #[should_panic(expected = "ゼロ除算")]
              fn test_divide_by_zero_message() {
                  divide(10, 0);
              }
              }

              Result を返すテスト

              #![allow(unused)]
              fn main() {
              #[test]
              fn test_with_result() -> Result<(), String> {
                  if 2 + 2 == 4 {
                      Ok(())
                  } else {
                      Err(String::from("計算が間違っています"))
                  }
              }
              }

              テストの構成

              テストモジュール

              #![allow(unused)]
              fn main() {
              // src/lib.rs
              pub fn public_function() -> i32 {
                  private_function() + 1
              }
              
              fn private_function() -> i32 {
                  42
              }
              
              #[cfg(test)]
              mod tests {
                  use super::*;
              
                  #[test]
                  fn test_public() {
                      assert_eq!(public_function(), 43);
                  }
              
                  #[test]
                  fn test_private() {
                      // 同じモジュール内なのでプライベート関数もテスト可能
                      assert_eq!(private_function(), 42);
                  }
              }
              }

              テストのフィルタリング

              # すべてのテスト
              cargo test
              
              # 特定のテストのみ
              cargo test test_add
              
              # 特定のモジュールのテスト
              cargo test tests::
              
              # 無視されたテストを実行
              cargo test -- --ignored
              

              テストの無視

              #![allow(unused)]
              fn main() {
              #[test]
              #[ignore]
              fn expensive_test() {
                  // 時間のかかるテスト
              }
              }

              統合テスト

              プロジェクト全体をテストします。

              my_project/
              ├── src/
              │   └── lib.rs
              └── tests/
                  └── integration_test.rs
              

              tests/integration_test.rs

              #![allow(unused)]
              fn main() {
              use my_project;
              
              #[test]
              fn test_integration() {
                  assert_eq!(my_project::public_function(), 43);
              }
              }

              テストのベストプラクティス

              1. AAAパターン

              #![allow(unused)]
              fn main() {
              #[test]
              fn test_user_creation() {
                  // Arrange(準備)
                  let name = "太郎";
                  let age = 25;
              
                  // Act(実行)
                  let user = User::new(name.to_string(), age);
              
                  // Assert(検証)
                  assert_eq!(user.name, "太郎");
                  assert_eq!(user.age, 25);
              }
              }

              2. 境界値テスト

              #![allow(unused)]
              fn main() {
              #[test]
              fn test_is_adult() {
                  assert!(!is_adult(17));  // 境界の下
                  assert!(is_adult(18));   // 境界
                  assert!(is_adult(19));   // 境界の上
              }
              }

              3. エラーケースのテスト

              #![allow(unused)]
              fn main() {
              #[test]
              fn test_parse_error() {
                  let result = parse_number("not a number");
                  assert!(result.is_err());
              }
              
              #[test]
              fn test_parse_success() {
                  let result = parse_number("42");
                  assert_eq!(result.unwrap(), 42);
              }
              }

              テストヘルパー

              共通のセットアップ

              #![allow(unused)]
              fn main() {
              #[cfg(test)]
              mod tests {
                  use super::*;
              
                  fn setup() -> User {
                      User::new("テスト太郎".to_string(), 20)
                  }
              
                  #[test]
                  fn test_user_name() {
                      let user = setup();
                      assert_eq!(user.name, "テスト太郎");
                  }
              
                  #[test]
                  fn test_user_age() {
                      let user = setup();
                      assert_eq!(user.age, 20);
                  }
              }
              }

              テストカバレッジ

              # cargo-tarpaulinをインストール
              cargo install cargo-tarpaulin
              
              # カバレッジレポート
              cargo tarpaulin
              

              まとめ

              機能説明
              #[test]テスト関数を示す
              assert!条件がtrueか検証
              assert_eq!等値を検証
              assert_ne!不等値を検証
              #[should_panic]パニックを期待
              #[ignore]テストを無視
              #[cfg(test)]テスト時のみコンパイル

              確認テスト

              Q1. `assert_eq!(a, b)`と`assert_ne!(a, b)`の違いは?

              Q2. `#[should_panic]`の用途は?

              Q3. 以下のテストの結果は?
              #[test] fn test() { assert_eq!(2 + 2, 5); }

              Q4. `assert!(is_even(3))`が失敗する理由は?(is_evenは偶数でtrueを返す関数)

              Q5. 統合テストを配置する正しいディレクトリは?


              次のドキュメント: 04_documentation.md

              ドキュメンテーション

              Rustのドキュメント機能を学びます。

              ドキュメントコメント

              アイテムのドキュメント(///)

              #![allow(unused)]
              fn main() {
              /// 2つの数値を加算します。
              ///
              /// # Arguments
              ///
              /// * `a` - 1つ目の数値
              /// * `b` - 2つ目の数値
              ///
              /// # Returns
              ///
              /// 2つの数値の合計
              ///
              /// # Examples
              ///
              /// ```
              /// let result = add(2, 3);
              /// assert_eq!(result, 5);
              /// ```
              pub fn add(a: i32, b: i32) -> i32 {
                  a + b
              }
              }

              モジュール/クレートのドキュメント(//!)

              #![allow(unused)]
              fn main() {
              //! # My Crate
              //!
              //! `my_crate` は便利な機能を提供するクレートです。
              //!
              //! ## 使い方
              //!
              //! ```rust
              //! use my_crate::add;
              //! let sum = add(1, 2);
              //! ```
              
              /// 加算関数
              pub fn add(a: i32, b: i32) -> i32 {
                  a + b
              }
              }

              ドキュメントのセクション

              よく使うセクション

              #![allow(unused)]
              fn main() {
              /// 短い説明。
              ///
              /// 詳しい説明をここに書きます。
              /// 複数行にわたって書けます。
              ///
              /// # Arguments
              ///
              /// 引数の説明
              ///
              /// # Returns
              ///
              /// 戻り値の説明
              ///
              /// # Errors
              ///
              /// エラーが発生する条件
              ///
              /// # Panics
              ///
              /// パニックする条件
              ///
              /// # Examples
              ///
              /// 使用例
              ///
              /// # Safety
              ///
              /// unsafeな理由(unsafe関数の場合)
              pub fn some_function() {}
              }

              ドキュメント生成

              # ドキュメント生成
              cargo doc
              
              # 生成してブラウザで開く
              cargo doc --open
              
              # 依存クレートも含める
              cargo doc --document-private-items
              

              ドキュメントテスト

              Examplesに書いたコードは自動的にテストされます。

              #![allow(unused)]
              fn main() {
              /// 除算を行います。
              ///
              /// # Examples
              ///
              /// ```
              /// let result = divide(10, 2);
              /// assert_eq!(result, Some(5));
              /// ```
              ///
              /// ゼロで割るとNoneを返します:
              ///
              /// ```
              /// let result = divide(10, 0);
              /// assert_eq!(result, None);
              /// ```
              pub fn divide(a: i32, b: i32) -> Option<i32> {
                  if b == 0 {
                      None
                  } else {
                      Some(a / b)
                  }
              }
              }
              # ドキュメントテストを実行
              cargo test --doc
              

              テストを隠す

              #![allow(unused)]
              fn main() {
              /// # Examples
              ///
              /// ```
              /// # // この行はドキュメントに表示されない
              /// # fn setup() -> i32 { 42 }
              /// let value = setup();
              /// assert_eq!(value, 42);
              /// ```
              }

              コンパイルのみ(実行しない)

              #![allow(unused)]
              fn main() {
              /// ```no_run
              /// // コンパイルはするが実行しない
              /// std::process::exit(1);
              /// ```
              }

              コンパイルエラーを期待

              #![allow(unused)]
              fn main() {
              /// ```compile_fail
              /// let x: i32 = "hello";
              /// ```
              }

              構造体のドキュメント

              #![allow(unused)]
              fn main() {
              /// ユーザーを表す構造体。
              ///
              /// # Examples
              ///
              /// ```
              /// use my_crate::User;
              ///
              /// let user = User::new("太郎".to_string(), 25);
              /// assert_eq!(user.name(), "太郎");
              /// ```
              pub struct User {
                  /// ユーザー名
                  name: String,
                  /// 年齢
                  age: u32,
              }
              
              impl User {
                  /// 新しいユーザーを作成します。
                  ///
                  /// # Arguments
                  ///
                  /// * `name` - ユーザー名
                  /// * `age` - 年齢
                  pub fn new(name: String, age: u32) -> Self {
                      Self { name, age }
                  }
              
                  /// ユーザー名を返します。
                  pub fn name(&self) -> &str {
                      &self.name
                  }
              }
              }

              リンク

              #![allow(unused)]
              fn main() {
              /// [`Vec`]を使った例。
              ///
              /// 詳細は[`std::collections::HashMap`]を参照。
              ///
              /// [`add`]関数も参照してください。
              pub fn example() {}
              
              /// 加算関数
              pub fn add(a: i32, b: i32) -> i32 {
                  a + b
              }
              }

              ドキュメントのベストプラクティス

              1. 最初の行は簡潔に

              #![allow(unused)]
              fn main() {
              /// ファイルを読み込んで内容を返します。
              ///
              /// 詳しい説明...
              }

              2. Examplesは動作するコードを

              #![allow(unused)]
              fn main() {
              /// # Examples
              ///
              /// ```
              /// use my_crate::Config;
              ///
              /// let config = Config::default();
              /// assert!(config.is_valid());
              /// ```
              }

              3. 公開APIは必ずドキュメント化

              #![allow(unused)]
              #![deny(missing_docs)]  // ドキュメントがないとエラー
              
              fn main() {
              /// 公開関数
              pub fn public_function() {}
              }

              まとめ

              構文用途
              ///アイテムのドキュメント
              //!モジュール/クレートのドキュメント
              # Examples使用例(自動テスト)
              cargo docドキュメント生成
              cargo test --docドキュメントテスト

              確認テスト

              Q1. `///`と`//!`の違いは?

              Q2. ドキュメントの`# Examples`セクションの特徴は?

              Q3. コードブロックに付ける`no_run`の意味は?

              Q4. `double(5)`が10を返す関数で、`assert_eq!(double(5), 11)`のドキュメントテストが失敗する理由は?

              Q5. ドキュメントを生成してブラウザで開くコマンドは?


              次のドキュメント: 05_async_basics.md

              非同期プログラミング入門

              Rustの非同期処理(async/await)の基礎を学びます。

              なぜ非同期処理が必要か

              同期処理の問題

              #![allow(unused)]
              fn main() {
              // 同期処理:1つずつ順番に実行
              fn fetch_data() {
                  let data1 = fetch_from_server1();  // 2秒待つ
                  let data2 = fetch_from_server2();  // 2秒待つ
                  let data3 = fetch_from_server3();  // 2秒待つ
                  // 合計: 6秒
              }
              }

              非同期処理の利点

              #![allow(unused)]
              fn main() {
              // 非同期処理:待ち時間を有効活用
              async fn fetch_data() {
                  let (data1, data2, data3) = tokio::join!(
                      fetch_from_server1(),
                      fetch_from_server2(),
                      fetch_from_server3(),
                  );
                  // 合計: 約2秒(並行実行)
              }
              }

              async/await の基本

              async関数の定義

              #![allow(unused)]
              fn main() {
              // async関数はFutureを返す
              async fn hello() -> String {
                  "Hello, async world!".to_string()
              }
              
              // 上記は以下と同等
              fn hello() -> impl Future<Output = String> {
                  async {
                      "Hello, async world!".to_string()
                  }
              }
              }

              awaitで結果を取得

              async fn greet(name: &str) -> String {
                  format!("Hello, {}!", name)
              }
              
              async fn main_async() {
                  let greeting = greet("Rust").await;  // .awaitで結果を取得
                  println!("{}", greeting);
              }

              Tokioランタイム

              非同期コードを実行するには「ランタイム」が必要です。

              セットアップ

              Cargo.toml

              [dependencies]
              tokio = { version = "1", features = ["full"] }
              

              基本的な使い方

              use tokio;
              
              #[tokio::main]
              async fn main() {
                  println!("非同期処理を開始");
              
                  let result = async_task().await;
                  println!("結果: {}", result);
              }
              
              async fn async_task() -> i32 {
                  // 非同期処理
                  42
              }

              #[tokio::main]の意味

              // これは...
              #[tokio::main]
              async fn main() {
                  // 非同期コード
              }
              
              // こう展開される
              fn main() {
                  tokio::runtime::Runtime::new()
                      .unwrap()
                      .block_on(async {
                          // 非同期コード
                      })
              }

              非同期の待機

              tokio::time::sleep

              use tokio::time::{sleep, Duration};
              
              async fn delayed_hello() {
                  println!("3秒後に挨拶します...");
                  sleep(Duration::from_secs(3)).await;
                  println!("Hello!");
              }
              
              #[tokio::main]
              async fn main() {
                  delayed_hello().await;
              }

              注意: std::thread::sleepとの違い

              #![allow(unused)]
              fn main() {
              use std::thread;
              use tokio::time::{sleep, Duration};
              
              // ❌ これはスレッド全体をブロック
              async fn bad_sleep() {
                  thread::sleep(Duration::from_secs(1));  // 他のタスクも止まる
              }
              
              // ✅ これは他のタスクに処理を譲る
              async fn good_sleep() {
                  sleep(Duration::from_secs(1)).await;  // 他のタスクは動ける
              }
              }

              並行実行

              tokio::join! - 複数のFutureを並行実行

              use tokio::time::{sleep, Duration};
              
              async fn task1() -> i32 {
                  sleep(Duration::from_secs(1)).await;
                  println!("Task 1 完了");
                  1
              }
              
              async fn task2() -> i32 {
                  sleep(Duration::from_secs(2)).await;
                  println!("Task 2 完了");
                  2
              }
              
              #[tokio::main]
              async fn main() {
                  let start = std::time::Instant::now();
              
                  // 並行実行(合計約2秒)
                  let (result1, result2) = tokio::join!(task1(), task2());
              
                  println!("結果: {}, {}", result1, result2);
                  println!("経過時間: {:?}", start.elapsed());
              }

              tokio::select! - 最初に完了したものを処理

              use tokio::time::{sleep, Duration};
              
              async fn fast_task() -> &'static str {
                  sleep(Duration::from_millis(100)).await;
                  "fast"
              }
              
              async fn slow_task() -> &'static str {
                  sleep(Duration::from_secs(10)).await;
                  "slow"
              }
              
              #[tokio::main]
              async fn main() {
                  tokio::select! {
                      result = fast_task() => {
                          println!("Fast finished: {}", result);
                      }
                      result = slow_task() => {
                          println!("Slow finished: {}", result);
                      }
                  }
                  // "Fast finished: fast" と表示(slowはキャンセル)
              }

              非同期でHTTPリクエスト

              reqwestクレートを使用

              Cargo.toml

              [dependencies]
              tokio = { version = "1", features = ["full"] }
              reqwest = { version = "0.11", features = ["json"] }
              serde = { version = "1.0", features = ["derive"] }
              serde_json = "1.0"
              

              基本的なGETリクエスト

              use reqwest;
              
              #[tokio::main]
              async fn main() -> Result<(), reqwest::Error> {
                  let response = reqwest::get("https://httpbin.org/get")
                      .await?
                      .text()
                      .await?;
              
                  println!("Response: {}", response);
                  Ok(())
              }

              JSONレスポンスの解析

              use reqwest;
              use serde::Deserialize;
              
              #[derive(Debug, Deserialize)]
              struct ApiResponse {
                  origin: String,
                  url: String,
              }
              
              #[tokio::main]
              async fn main() -> Result<(), reqwest::Error> {
                  let response: ApiResponse = reqwest::get("https://httpbin.org/get")
                      .await?
                      .json()
                      .await?;
              
                  println!("Origin: {}", response.origin);
                  println!("URL: {}", response.url);
                  Ok(())
              }

              複数のAPIを並行で呼び出し

              use reqwest;
              
              async fn fetch_url(url: &str) -> Result<String, reqwest::Error> {
                  let response = reqwest::get(url).await?.text().await?;
                  Ok(response)
              }
              
              #[tokio::main]
              async fn main() -> Result<(), reqwest::Error> {
                  let urls = vec![
                      "https://httpbin.org/get",
                      "https://httpbin.org/ip",
                      "https://httpbin.org/user-agent",
                  ];
              
                  // 並行でフェッチ
                  let futures: Vec<_> = urls.iter()
                      .map(|url| fetch_url(url))
                      .collect();
              
                  let results = futures::future::join_all(futures).await;
              
                  for (url, result) in urls.iter().zip(results) {
                      match result {
                          Ok(body) => println!("{}: {} bytes", url, body.len()),
                          Err(e) => println!("{}: エラー - {}", url, e),
                      }
                  }
              
                  Ok(())
              }

              エラーハンドリング

              Result と ?演算子

              use reqwest;
              use thiserror::Error;
              
              #[derive(Error, Debug)]
              enum AppError {
                  #[error("HTTPエラー: {0}")]
                  Http(#[from] reqwest::Error),
              
                  #[error("JSONパースエラー: {0}")]
                  Json(#[from] serde_json::Error),
              }
              
              async fn fetch_data(url: &str) -> Result<String, AppError> {
                  let response = reqwest::get(url).await?;
                  let text = response.text().await?;
                  Ok(text)
              }
              
              #[tokio::main]
              async fn main() {
                  match fetch_data("https://httpbin.org/get").await {
                      Ok(data) => println!("成功: {} bytes", data.len()),
                      Err(e) => eprintln!("エラー: {}", e),
                  }
              }

              タイムアウト

              use tokio::time::{timeout, Duration};
              use reqwest;
              
              #[tokio::main]
              async fn main() {
                  let result = timeout(
                      Duration::from_secs(5),
                      reqwest::get("https://httpbin.org/delay/10")
                  ).await;
              
                  match result {
                      Ok(Ok(response)) => println!("成功: {}", response.status()),
                      Ok(Err(e)) => println!("リクエストエラー: {}", e),
                      Err(_) => println!("タイムアウト!"),
                  }
              }

              非同期のベストプラクティス

              1. ブロッキング処理を避ける

              #![allow(unused)]
              fn main() {
              // ❌ 悪い例
              async fn bad_example() {
                  std::thread::sleep(Duration::from_secs(1));  // ブロック
                  std::fs::read_to_string("file.txt");         // ブロック
              }
              
              // ✅ 良い例
              async fn good_example() {
                  tokio::time::sleep(Duration::from_secs(1)).await;
                  tokio::fs::read_to_string("file.txt").await;
              }
              }

              2. 重い処理は spawn_blocking

              #![allow(unused)]
              fn main() {
              use tokio::task;
              
              async fn compute_heavy() -> i32 {
                  // CPU重い処理は別スレッドで
                  task::spawn_blocking(|| {
                      // 重い計算
                      (0..1_000_000).sum()
                  }).await.unwrap()
              }
              }

              3. エラーは早めに処理

              #![allow(unused)]
              fn main() {
              async fn fetch_and_process(url: &str) -> Result<String, Error> {
                  let response = reqwest::get(url).await?;
              
                  // ステータスチェック
                  if !response.status().is_success() {
                      return Err(Error::HttpStatus(response.status()));
                  }
              
                  let text = response.text().await?;
                  Ok(text)
              }
              }

              まとめ

              概念説明
              async非同期関数を定義
              .awaitFutureの完了を待つ
              tokio非同期ランタイム
              join!複数を並行実行
              select!最初の完了を処理
              注意点対策
              ブロッキングtokio::time::sleep等を使用
              CPU重い処理spawn_blockingを使用
              エラー処理?演算子とResultを活用

              確認テスト

              Q1. async関数が返すものは?

              Q2. tokio::join!(task1(), task2())の動作は?

              Q3. 以下のコードの実行時間は約何秒?
              tokio::join!(task_a(), task_b())(task_aは2秒sleep、task_bは3秒sleep)

              Q4. 非同期関数内でstd::thread::sleepを使うとどうなる?

              Q5. tokio::select!の動作として正しいのは?


              Phase 4 完了!

              おめでとうございます!Phase 4を完了しました。

              学んだこと:

              • Cargoの高度な機能(ワークスペース、features)
              • クレートエコシステムの活用
              • テストの書き方(単体テスト、統合テスト)
              • ドキュメンテーション
              • 非同期プログラミング(async/await、tokio)

              次のPhase: Phase 5: Webアプリ開発

              Web基礎

              Webアプリケーション開発の基礎概念を学びます。

              Webの仕組み

              クライアントとサーバー

              ┌─────────────┐         HTTP         ┌─────────────┐
              │  クライアント │  ──── リクエスト ────→ │   サーバー   │
              │  (ブラウザ)   │  ←──── レスポンス ──── │  (Rustアプリ) │
              └─────────────┘                      └─────────────┘
              
              • クライアント: ブラウザやアプリ。リクエストを送信
              • サーバー: リクエストを受け取り、レスポンスを返す

              HTTPプロトコル

              HTTPリクエスト

              GET /users/123 HTTP/1.1
              Host: api.example.com
              Content-Type: application/json
              Authorization: Bearer token123
              
              {リクエストボディ}
              

              構成要素:

              1. メソッド: GET, POST, PUT, DELETE など
              2. パス: /users/123
              3. ヘッダー: メタ情報
              4. ボディ: 送信データ(POST/PUTなど)

              HTTPレスポンス

              HTTP/1.1 200 OK
              Content-Type: application/json
              
              {"id": 123, "name": "Taro"}
              

              構成要素:

              1. ステータスコード: 200, 404, 500 など
              2. ヘッダー: メタ情報
              3. ボディ: レスポンスデータ

              HTTPメソッド

              メソッド用途
              GETデータ取得ユーザー情報を取得
              POSTデータ作成新規ユーザー登録
              PUTデータ更新(全体)ユーザー情報を完全置換
              PATCHデータ更新(部分)メールアドレスのみ更新
              DELETEデータ削除ユーザーを削除

              ステータスコード

              2xx: 成功

              コード意味用途
              200OK成功(一般的)
              201Createdリソース作成成功
              204No Content成功(レスポンスボディなし)

              4xx: クライアントエラー

              コード意味用途
              400Bad Requestリクエストが不正
              401Unauthorized認証が必要
              403Forbiddenアクセス権限なし
              404Not Foundリソースが見つからない
              422Unprocessable Entityバリデーションエラー

              5xx: サーバーエラー

              コード意味用途
              500Internal Server Errorサーバー内部エラー
              502Bad Gateway上流サーバーエラー
              503Service Unavailableサービス利用不可

              REST API

              RESTful APIは、HTTPを使ってリソースを操作する設計パターンです。

              REST設計の原則

              1. リソース指向: URLはリソースを表す(動詞ではなく名詞)
              2. HTTPメソッドで操作を表現: GET/POST/PUT/DELETE
              3. ステートレス: 各リクエストは独立

              REST APIの例

              # ユーザー一覧を取得
              GET /users
              
              # 特定のユーザーを取得
              GET /users/123
              
              # 新しいユーザーを作成
              POST /users
              Body: {"name": "Taro", "email": "taro@example.com"}
              
              # ユーザーを更新
              PUT /users/123
              Body: {"name": "Taro", "email": "new@example.com"}
              
              # ユーザーを削除
              DELETE /users/123
              

              URLの設計

              # 良い例(名詞、複数形)
              GET /users
              GET /users/123
              GET /users/123/posts
              GET /posts?author=123
              
              # 悪い例(動詞が入っている)
              GET /getUsers
              POST /createUser
              GET /getUserById/123
              

              JSON

              Web APIでよく使われるデータ形式です。

              JSON形式

              {
                "id": 123,
                "name": "Taro",
                "email": "taro@example.com",
                "age": 25,
                "active": true,
                "tags": ["rust", "web"],
                "address": {
                  "city": "Tokyo",
                  "zip": "100-0001"
                }
              }
              

              RustでのJSON処理(serde)

              #![allow(unused)]
              fn main() {
              use serde::{Deserialize, Serialize};
              
              #[derive(Debug, Serialize, Deserialize)]
              struct User {
                  id: u32,
                  name: String,
                  email: String,
              }
              
              // JSONからRustの構造体へ
              let json = r#"{"id": 1, "name": "Taro", "email": "taro@example.com"}"#;
              let user: User = serde_json::from_str(json).unwrap();
              
              // Rustの構造体からJSONへ
              let json_output = serde_json::to_string(&user).unwrap();
              }

              Webアプリケーションの種類

              1. APIサーバー(バックエンド)

              クライアント ──JSON── APIサーバー ── データベース
              
              • JSONでデータをやり取り
              • フロントエンドと分離
              • 今回学ぶのはこれ

              2. サーバーサイドレンダリング(SSR)

              ブラウザ ←─HTML─ サーバー ── データベース
              
              • サーバーでHTMLを生成
              • 従来型のWebアプリ

              3. 静的サイト + API

              ブラウザ ←─HTML/JS─ 静的ファイル
                  │
                  └──JSON── APIサーバー
              
              • HTML/JSは静的配信
              • データはAPIから取得

              Rustでの Web開発

              主要なWebフレームワーク

              フレームワーク特徴
              Axumモダン、tokio製、型安全
              Actix-web高性能、成熟
              Rocket使いやすさ重視
              Warpシンプル、コンポーザブル

              このカリキュラムでは Axum を使用します。

              なぜAxumか

              • tokioチームが開発(非同期との相性が良い)
              • 型システムを活かした安全な設計
              • モダンなRustの機能を活用
              • 活発なコミュニティ

              まとめ

              概念説明
              HTTPクライアント-サーバー間の通信プロトコル
              RESTリソース指向のAPI設計パターン
              JSONデータ交換フォーマット
              ステータスコード処理結果を表す数字
              Rustでの対応
              HTTPサーバー → Axum
              JSON処理 → serde
              非同期 → tokio

              確認テスト

              Q1. RESTful APIで「新しいユーザーを作成する」場合、適切なHTTPメソッドは?

              Q2. HTTPステータスコード「404」が意味するのは?

              Q3. 以下のAPIエンドポイント設計で、RESTの原則に反しているものは?

              Q4. 以下のJSON文字列で構文エラーがあるのはどれ? { 'name': "Taro", "age": 25, "active": True }

              Q5. ブックマーク管理APIで「特定のブックマークを更新する」場合、RESTfulなエンドポイント設計は?


              次のドキュメント: 02_axum_intro.md

              Axum入門

              RustのモダンなWebフレームワーク「Axum」を学びます。

              Axumとは

              Axumは、tokioチームが開発したWebフレームワークです。

              特徴

              • 型安全: コンパイル時に多くのエラーを検出
              • 非同期: tokioベースで高性能
              • モジュラー: 必要な機能だけ使える
              • エコシステム: tower/hyperとの連携

              セットアップ

              Cargo.toml

              [dependencies]
              axum = "0.7"
              tokio = { version = "1", features = ["full"] }
              serde = { version = "1.0", features = ["derive"] }
              serde_json = "1.0"
              

              Hello World

              use axum::{routing::get, Router};
              
              #[tokio::main]
              async fn main() {
                  // ルーターを作成
                  let app = Router::new()
                      .route("/", get(hello));
              
                  // サーバーを起動
                  let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
                  println!("Server running on http://localhost:3000");
                  axum::serve(listener, app).await.unwrap();
              }
              
              // ハンドラー関数
              async fn hello() -> &'static str {
                  "Hello, Axum!"
              }

              実行:

              cargo run
              # 別のターミナルで
              curl http://localhost:3000
              # => Hello, Axum!
              

              基本構造

              use axum::{routing::get, Router};
              
              #[tokio::main]
              async fn main() {
                  let app = Router::new()
                      .route("/", get(handler1))           // GET /
                      .route("/users", get(handler2))      // GET /users
                      .route("/users", post(handler3));    // POST /users
              
                  // サーバー起動...
              }

              ルーティングのパターン

              #![allow(unused)]
              fn main() {
              use axum::routing::{get, post, put, delete};
              
              let app = Router::new()
                  // 基本的なルート
                  .route("/", get(root))
              
                  // CRUDパターン
                  .route("/users", get(list_users))          // GET /users
                  .route("/users", post(create_user))        // POST /users
                  .route("/users/:id", get(get_user))        // GET /users/123
                  .route("/users/:id", put(update_user))     // PUT /users/123
                  .route("/users/:id", delete(delete_user)); // DELETE /users/123
              }

              ハンドラー関数

              基本形

              #![allow(unused)]
              fn main() {
              async fn handler() -> impl IntoResponse {
                  "Hello, World!"
              }
              }

              戻り値の種類

              #![allow(unused)]
              fn main() {
              use axum::response::{Html, Json};
              use axum::http::StatusCode;
              
              // 文字列
              async fn text() -> &'static str {
                  "Plain text"
              }
              
              // HTML
              async fn html() -> Html<&'static str> {
                  Html("<h1>Hello</h1>")
              }
              
              // JSON
              async fn json() -> Json<serde_json::Value> {
                  Json(serde_json::json!({"message": "Hello"}))
              }
              
              // ステータスコード付き
              async fn with_status() -> (StatusCode, &'static str) {
                  (StatusCode::CREATED, "Created!")
              }
              
              // エラー
              async fn not_found() -> StatusCode {
                  StatusCode::NOT_FOUND
              }
              }

              JSONレスポンス

              構造体をJSONで返す

              #![allow(unused)]
              fn main() {
              use axum::Json;
              use serde::Serialize;
              
              #[derive(Serialize)]
              struct User {
                  id: u32,
                  name: String,
              }
              
              async fn get_user() -> Json<User> {
                  let user = User {
                      id: 1,
                      name: "Taro".to_string(),
                  };
                  Json(user)
              }
              }

              レスポンス例

              {
                "id": 1,
                "name": "Taro"
              }
              

              パスパラメータ

              URLの一部を変数として受け取ります。

              #![allow(unused)]
              fn main() {
              use axum::extract::Path;
              
              // /users/123 → id = 123
              async fn get_user(Path(id): Path<u32>) -> String {
                  format!("User ID: {}", id)
              }
              
              // /users/123/posts/456 → 複数のパラメータ
              async fn get_post(Path((user_id, post_id)): Path<(u32, u32)>) -> String {
                  format!("User: {}, Post: {}", user_id, post_id)
              }
              }

              ルート定義

              #![allow(unused)]
              fn main() {
              let app = Router::new()
                  .route("/users/:id", get(get_user))
                  .route("/users/:user_id/posts/:post_id", get(get_post));
              }

              クエリパラメータ

              ?key=value 形式のパラメータを受け取ります。

              #![allow(unused)]
              fn main() {
              use axum::extract::Query;
              use serde::Deserialize;
              
              #[derive(Deserialize)]
              struct Pagination {
                  page: Option<u32>,
                  limit: Option<u32>,
              }
              
              // /users?page=1&limit=10
              async fn list_users(Query(params): Query<Pagination>) -> String {
                  let page = params.page.unwrap_or(1);
                  let limit = params.limit.unwrap_or(10);
                  format!("Page: {}, Limit: {}", page, limit)
              }
              }

              JSONリクエストボディ

              POSTやPUTでJSONデータを受け取ります。

              #![allow(unused)]
              fn main() {
              use axum::Json;
              use serde::Deserialize;
              
              #[derive(Deserialize)]
              struct CreateUser {
                  name: String,
                  email: String,
              }
              
              async fn create_user(Json(payload): Json<CreateUser>) -> String {
                  format!("Created user: {} ({})", payload.name, payload.email)
              }
              }

              リクエスト例

              curl -X POST http://localhost:3000/users \
                -H "Content-Type: application/json" \
                -d '{"name": "Taro", "email": "taro@example.com"}'
              

              完全な例: CRUD API

              use axum::{
                  extract::{Path, Query, State},
                  http::StatusCode,
                  routing::{get, post, put, delete},
                  Json, Router,
              };
              use serde::{Deserialize, Serialize};
              use std::sync::Arc;
              use tokio::sync::RwLock;
              
              // データモデル
              #[derive(Debug, Clone, Serialize, Deserialize)]
              struct User {
                  id: u32,
                  name: String,
                  email: String,
              }
              
              #[derive(Deserialize)]
              struct CreateUserRequest {
                  name: String,
                  email: String,
              }
              
              // アプリケーション状態(簡易的なインメモリDB)
              type AppState = Arc<RwLock<Vec<User>>>;
              
              #[tokio::main]
              async fn main() {
                  let state: AppState = Arc::new(RwLock::new(vec![]));
              
                  let app = Router::new()
                      .route("/users", get(list_users))
                      .route("/users", post(create_user))
                      .route("/users/:id", get(get_user))
                      .route("/users/:id", delete(delete_user))
                      .with_state(state);
              
                  let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
                  println!("Server running on http://localhost:3000");
                  axum::serve(listener, app).await.unwrap();
              }
              
              // ユーザー一覧
              async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
                  let users = state.read().await;
                  Json(users.clone())
              }
              
              // ユーザー作成
              async fn create_user(
                  State(state): State<AppState>,
                  Json(payload): Json<CreateUserRequest>,
              ) -> (StatusCode, Json<User>) {
                  let mut users = state.write().await;
              
                  let id = users.len() as u32 + 1;
                  let user = User {
                      id,
                      name: payload.name,
                      email: payload.email,
                  };
              
                  users.push(user.clone());
              
                  (StatusCode::CREATED, Json(user))
              }
              
              // ユーザー取得
              async fn get_user(
                  State(state): State<AppState>,
                  Path(id): Path<u32>,
              ) -> Result<Json<User>, StatusCode> {
                  let users = state.read().await;
              
                  users
                      .iter()
                      .find(|u| u.id == id)
                      .cloned()
                      .map(Json)
                      .ok_or(StatusCode::NOT_FOUND)
              }
              
              // ユーザー削除
              async fn delete_user(
                  State(state): State<AppState>,
                  Path(id): Path<u32>,
              ) -> StatusCode {
                  let mut users = state.write().await;
                  let original_len = users.len();
              
                  users.retain(|u| u.id != id);
              
                  if users.len() < original_len {
                      StatusCode::NO_CONTENT
                  } else {
                      StatusCode::NOT_FOUND
                  }
              }

              テスト

              # ユーザー作成
              curl -X POST http://localhost:3000/users \
                -H "Content-Type: application/json" \
                -d '{"name": "Taro", "email": "taro@example.com"}'
              
              # ユーザー一覧
              curl http://localhost:3000/users
              
              # 特定のユーザー取得
              curl http://localhost:3000/users/1
              
              # ユーザー削除
              curl -X DELETE http://localhost:3000/users/1
              

              まとめ

              概念説明
              Routerルーティング定義
              Handlerリクエスト処理関数
              PathURLパラメータ抽出
              Queryクエリパラメータ抽出
              JsonJSON送受信
              Stateアプリケーション状態
              Extractor用途
              Path<T>/users/:id からIDを取得
              Query<T>?page=1 からパラメータ取得
              Json<T>リクエストボディをパース
              State<T>共有状態にアクセス

              確認テスト

              Q1. Axumでパスパラメータ /users/123 からIDを取得するのに使うExtractorは?

              Q2. Axumで POST /users にJSONデータを受け取る場合、正しいハンドラーの引数の型は?

              Q3. Query(p): Query<Params>(page: u32, limit: u32)で /users?page=2&limit=5 にアクセスしたとき、format!("{}-{}", p.page, p.limit) の出力は?

              Q4. 以下のコードのエラー原因は? async fn hello() -> String { "Hello" } fn main() { let app = Router::new().route("/", get(hello)); }

              Q5. GET /hello/:name で "Hello, {name}!" を返すハンドラーの正しい実装は?


              次のドキュメント: 03_routing_handlers.md

              ルーティングとハンドラー

              Axumのルーティングとハンドラーの詳細を学びます。

              ルーティングの基本

              単一ルート

              #![allow(unused)]
              fn main() {
              use axum::{routing::get, Router};
              
              let app = Router::new()
                  .route("/", get(handler));
              }

              複数のHTTPメソッド

              #![allow(unused)]
              fn main() {
              use axum::routing::{get, post, put, delete};
              
              let app = Router::new()
                  .route("/users", get(list_users).post(create_user))
                  .route("/users/:id", get(get_user).put(update_user).delete(delete_user));
              }

              method_router

              #![allow(unused)]
              fn main() {
              use axum::routing::MethodRouter;
              
              // 同じパスに複数のメソッドを設定
              let user_routes = get(list_users)
                  .post(create_user);
              
              let app = Router::new()
                  .route("/users", user_routes);
              }

              パスパターン

              静的パス

              #![allow(unused)]
              fn main() {
              .route("/users", get(handler))      // /users
              .route("/api/v1/users", get(handler)) // /api/v1/users
              }

              動的パス(パラメータ)

              #![allow(unused)]
              fn main() {
              .route("/users/:id", get(handler))           // /users/123
              .route("/users/:id/posts/:post_id", get(handler)) // /users/123/posts/456
              }

              ワイルドカード

              #![allow(unused)]
              fn main() {
              .route("/files/*path", get(handler))  // /files/a/b/c → path = "a/b/c"
              }

              Extractor(データ抽出)

              Path: パスパラメータ

              #![allow(unused)]
              fn main() {
              use axum::extract::Path;
              
              // 単一パラメータ
              async fn get_user(Path(id): Path<u32>) -> String {
                  format!("User: {}", id)
              }
              
              // 複数パラメータ
              async fn get_post(Path((user_id, post_id)): Path<(u32, u32)>) -> String {
                  format!("User: {}, Post: {}", user_id, post_id)
              }
              
              // 構造体で受け取る
              #[derive(Deserialize)]
              struct PathParams {
                  user_id: u32,
                  post_id: u32,
              }
              
              async fn get_post_struct(Path(params): Path<PathParams>) -> String {
                  format!("User: {}, Post: {}", params.user_id, params.post_id)
              }
              }

              Query: クエリパラメータ

              #![allow(unused)]
              fn main() {
              use axum::extract::Query;
              use serde::Deserialize;
              
              #[derive(Deserialize)]
              struct ListParams {
                  page: Option<u32>,
                  limit: Option<u32>,
                  search: Option<String>,
              }
              
              // /users?page=1&limit=10&search=foo
              async fn list_users(Query(params): Query<ListParams>) -> String {
                  let page = params.page.unwrap_or(1);
                  let limit = params.limit.unwrap_or(10);
                  format!("Page: {}, Limit: {}, Search: {:?}", page, limit, params.search)
              }
              }

              Json: リクエストボディ

              #![allow(unused)]
              fn main() {
              use axum::Json;
              use serde::Deserialize;
              
              #[derive(Deserialize)]
              struct CreateUser {
                  name: String,
                  email: String,
                  #[serde(default)]
                  age: Option<u32>,
              }
              
              async fn create_user(Json(payload): Json<CreateUser>) -> String {
                  format!("Creating: {} ({})", payload.name, payload.email)
              }
              }

              Header: ヘッダー

              #![allow(unused)]
              fn main() {
              use axum::http::header::HeaderMap;
              use axum::extract::TypedHeader;
              use axum::headers::Authorization;
              use axum::headers::authorization::Bearer;
              
              // 全ヘッダー取得
              async fn all_headers(headers: HeaderMap) -> String {
                  format!("{:?}", headers)
              }
              
              // 特定のヘッダー
              async fn auth_header(
                  TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
              ) -> String {
                  format!("Token: {}", auth.token())
              }
              }

              State: 共有状態

              #![allow(unused)]
              fn main() {
              use axum::extract::State;
              use std::sync::Arc;
              
              struct AppState {
                  db_pool: Pool,
                  config: Config,
              }
              
              async fn handler(State(state): State<Arc<AppState>>) -> String {
                  // state.db_pool や state.config にアクセス
                  "OK".to_string()
              }
              
              // メインでStateを設定
              let state = Arc::new(AppState { ... });
              let app = Router::new()
                  .route("/", get(handler))
                  .with_state(state);
              }

              複数のExtractor

              Extractorは複数組み合わせられます。

              #![allow(unused)]
              fn main() {
              async fn handler(
                  State(state): State<AppState>,
                  Path(id): Path<u32>,
                  Query(params): Query<ListParams>,
                  Json(body): Json<CreateUser>,
              ) -> impl IntoResponse {
                  // すべてのデータにアクセス可能
                  "OK"
              }
              }

              順序の注意点

              JsonRequestなど、リクエストボディを消費するExtractorは最後に配置します。

              #![allow(unused)]
              fn main() {
              // ✅ 正しい: Jsonは最後
              async fn handler(
                  State(state): State<AppState>,
                  Path(id): Path<u32>,
                  Json(body): Json<Data>,  // 最後
              ) { }
              
              // ❌ 間違い: Jsonの後に他のExtractor
              async fn handler(
                  Json(body): Json<Data>,
                  Path(id): Path<u32>,  // エラー
              ) { }
              }

              レスポンス

              基本的なレスポンス型

              #![allow(unused)]
              fn main() {
              use axum::response::{Html, Json, IntoResponse};
              use axum::http::StatusCode;
              
              // 文字列
              async fn text() -> &'static str {
                  "Hello"
              }
              
              // HTML
              async fn html() -> Html<String> {
                  Html("<h1>Hello</h1>".to_string())
              }
              
              // JSON
              async fn json() -> Json<serde_json::Value> {
                  Json(serde_json::json!({"status": "ok"}))
              }
              
              // ステータスコードのみ
              async fn no_content() -> StatusCode {
                  StatusCode::NO_CONTENT
              }
              
              // ステータスコード + ボディ
              async fn created() -> (StatusCode, Json<User>) {
                  (StatusCode::CREATED, Json(user))
              }
              }

              カスタムレスポンス

              #![allow(unused)]
              fn main() {
              use axum::response::{Response, IntoResponse};
              use axum::http::{StatusCode, header};
              
              async fn custom_response() -> impl IntoResponse {
                  (
                      StatusCode::OK,
                      [(header::CONTENT_TYPE, "text/plain")],
                      "Hello with custom headers"
                  )
              }
              }

              Result型でエラー処理

              #![allow(unused)]
              fn main() {
              use axum::response::IntoResponse;
              use axum::http::StatusCode;
              
              async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, StatusCode> {
                  let user = find_user(id)
                      .ok_or(StatusCode::NOT_FOUND)?;
              
                  Ok(Json(user))
              }
              }

              エラーハンドリング

              カスタムエラー型

              #![allow(unused)]
              fn main() {
              use axum::response::{IntoResponse, Response};
              use axum::http::StatusCode;
              use axum::Json;
              
              // カスタムエラー型
              enum AppError {
                  NotFound,
                  BadRequest(String),
                  Internal(String),
              }
              
              // IntoResponseを実装
              impl IntoResponse for AppError {
                  fn into_response(self) -> Response {
                      let (status, message) = match self {
                          AppError::NotFound => (StatusCode::NOT_FOUND, "Not found"),
                          AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
                          AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.as_str()),
                      };
              
                      let body = Json(serde_json::json!({
                          "error": message
                      }));
              
                      (status, body).into_response()
                  }
              }
              
              // ハンドラーで使用
              async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
                  let user = find_user(id)
                      .ok_or(AppError::NotFound)?;
              
                  Ok(Json(user))
              }
              }

              anyhowとの連携

              #![allow(unused)]
              fn main() {
              use anyhow::Result;
              use axum::response::IntoResponse;
              
              // anyhowエラーをラップ
              struct AppError(anyhow::Error);
              
              impl IntoResponse for AppError {
                  fn into_response(self) -> Response {
                      (
                          StatusCode::INTERNAL_SERVER_ERROR,
                          format!("Error: {}", self.0)
                      ).into_response()
                  }
              }
              
              impl<E> From<E> for AppError
              where
                  E: Into<anyhow::Error>,
              {
                  fn from(err: E) -> Self {
                      Self(err.into())
                  }
              }
              
              async fn handler() -> Result<Json<Data>, AppError> {
                  let data = fetch_data()?;  // ?でエラーを伝播
                  Ok(Json(data))
              }
              }

              ルーターのネスト

              nest: サブルーターを結合

              #![allow(unused)]
              fn main() {
              // ユーザー関連のルート
              fn user_routes() -> Router {
                  Router::new()
                      .route("/", get(list_users).post(create_user))
                      .route("/:id", get(get_user).delete(delete_user))
              }
              
              // 投稿関連のルート
              fn post_routes() -> Router {
                  Router::new()
                      .route("/", get(list_posts).post(create_post))
                      .route("/:id", get(get_post))
              }
              
              // メインルーター
              let app = Router::new()
                  .nest("/users", user_routes())    // /users, /users/:id
                  .nest("/posts", post_routes());   // /posts, /posts/:id
              }

              merge: ルーターを統合

              #![allow(unused)]
              fn main() {
              let api_routes = Router::new()
                  .route("/users", get(list_users))
                  .route("/posts", get(list_posts));
              
              let web_routes = Router::new()
                  .route("/", get(home))
                  .route("/about", get(about));
              
              let app = api_routes.merge(web_routes);
              }

              ミドルウェア

              基本的なミドルウェア

              #![allow(unused)]
              fn main() {
              use axum::middleware::{self, Next};
              use axum::http::Request;
              use axum::response::Response;
              
              async fn logging_middleware<B>(
                  request: Request<B>,
                  next: Next<B>,
              ) -> Response {
                  println!("Request: {} {}", request.method(), request.uri());
              
                  let response = next.run(request).await;
              
                  println!("Response: {}", response.status());
                  response
              }
              
              let app = Router::new()
                  .route("/", get(handler))
                  .layer(middleware::from_fn(logging_middleware));
              }

              tower層の使用

              #![allow(unused)]
              fn main() {
              use tower_http::cors::CorsLayer;
              use tower_http::trace::TraceLayer;
              
              let app = Router::new()
                  .route("/", get(handler))
                  .layer(CorsLayer::permissive())
                  .layer(TraceLayer::new_for_http());
              }

              まとめ

              Extractor用途
              Path<T>URLパラメータ
              Query<T>クエリパラメータ
              Json<T>JSONボディ
              State<T>共有状態
              HeaderMap全ヘッダー
              Response用途
              &str / Stringテキスト
              Html<T>HTML
              Json<T>JSON
              StatusCodeステータスのみ
              (StatusCode, T)ステータス + ボディ

              確認テスト

              Q1. Axumで /users?page=2&search=foo からパラメータを取得するExtractorは?

              Q2. ハンドラーで複数のExtractorを使う場合、Jsonの配置について正しいのは?

              Q3. Path<Params>(user_id: u32, post_id: u32)で GET /users/123/posts/456 にアクセスしたとき、format!("U:{},P:{}", p.user_id, p.post_id) の出力は?

              Q4. 以下のコードのエラー原因は? async fn handler(Json(body): Json<Data>, Path(id): Path<u32>, State(state): State<AppState>)

              Q5. GET /items/:id でIDが0以下なら400 Bad Request、見つからなければ404 Not Foundを返す場合、戻り値の型として適切なのは?


              次のドキュメント: 04_database_sqlx.md

              データベースとSQLx

              RustでデータベースにアクセスするためのSQLxを学びます。

              SQLxとは

              SQLxは、コンパイル時にSQLクエリを検証できるRustのデータベースライブラリです。

              特徴

              • コンパイル時検証: SQLの誤りをコンパイル時に検出
              • 非同期対応: async/awaitに対応
              • マイグレーション: スキーマ管理機能内蔵
              • 複数DB対応: SQLite, PostgreSQL, MySQL

              このカリキュラムでは SQLite を使用します(セットアップが簡単)。

              セットアップ

              Cargo.toml

              [dependencies]
              sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] }
              tokio = { version = "1", features = ["full"] }
              

              SQLx CLI のインストール

              cargo install sqlx-cli --no-default-features --features sqlite
              

              データベースの作成

              # 環境変数を設定
              export DATABASE_URL="sqlite:./database.db"
              
              # データベースファイルを作成
              sqlx database create
              

              マイグレーション

              マイグレーションファイルの作成

              sqlx migrate add create_users_table
              

              migrations/YYYYMMDDHHMMSS_create_users_table.sql が作成されます。

              マイグレーションの内容

              -- migrations/20240101000000_create_users_table.sql
              
              CREATE TABLE IF NOT EXISTS users (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  name TEXT NOT NULL,
                  email TEXT NOT NULL UNIQUE,
                  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
              );
              

              マイグレーションの実行

              sqlx migrate run
              

              基本的な使い方

              接続

              use sqlx::sqlite::SqlitePool;
              
              #[tokio::main]
              async fn main() -> Result<(), sqlx::Error> {
                  let pool = SqlitePool::connect("sqlite:./database.db").await?;
              
                  // poolを使ってクエリを実行
              
                  Ok(())
              }

              INSERT

              #![allow(unused)]
              fn main() {
              use sqlx::sqlite::SqlitePool;
              
              async fn create_user(pool: &SqlitePool, name: &str, email: &str) -> Result<i64, sqlx::Error> {
                  let result = sqlx::query(
                      "INSERT INTO users (name, email) VALUES (?, ?)"
                  )
                  .bind(name)
                  .bind(email)
                  .execute(pool)
                  .await?;
              
                  Ok(result.last_insert_rowid())
              }
              }

              SELECT(単一行)

              #![allow(unused)]
              fn main() {
              use sqlx::{FromRow, sqlite::SqlitePool};
              
              #[derive(Debug, FromRow)]
              struct User {
                  id: i64,
                  name: String,
                  email: String,
              }
              
              async fn get_user(pool: &SqlitePool, id: i64) -> Result<Option<User>, sqlx::Error> {
                  let user = sqlx::query_as::<_, User>(
                      "SELECT id, name, email FROM users WHERE id = ?"
                  )
                  .bind(id)
                  .fetch_optional(pool)
                  .await?;
              
                  Ok(user)
              }
              }

              SELECT(複数行)

              #![allow(unused)]
              fn main() {
              async fn list_users(pool: &SqlitePool) -> Result<Vec<User>, sqlx::Error> {
                  let users = sqlx::query_as::<_, User>(
                      "SELECT id, name, email FROM users ORDER BY id"
                  )
                  .fetch_all(pool)
                  .await?;
              
                  Ok(users)
              }
              }

              UPDATE

              #![allow(unused)]
              fn main() {
              async fn update_user(
                  pool: &SqlitePool,
                  id: i64,
                  name: &str,
                  email: &str,
              ) -> Result<bool, sqlx::Error> {
                  let result = sqlx::query(
                      "UPDATE users SET name = ?, email = ? WHERE id = ?"
                  )
                  .bind(name)
                  .bind(email)
                  .bind(id)
                  .execute(pool)
                  .await?;
              
                  Ok(result.rows_affected() > 0)
              }
              }

              DELETE

              #![allow(unused)]
              fn main() {
              async fn delete_user(pool: &SqlitePool, id: i64) -> Result<bool, sqlx::Error> {
                  let result = sqlx::query("DELETE FROM users WHERE id = ?")
                      .bind(id)
                      .execute(pool)
                      .await?;
              
                  Ok(result.rows_affected() > 0)
              }
              }

              query_as マクロ

              構造体にマッピングする場合は query_as を使います。

              #![allow(unused)]
              fn main() {
              use sqlx::FromRow;
              
              #[derive(Debug, FromRow)]
              struct User {
                  id: i64,
                  name: String,
                  email: String,
              }
              
              // query_as を使用
              let users: Vec<User> = sqlx::query_as("SELECT * FROM users")
                  .fetch_all(&pool)
                  .await?;
              }

              コンパイル時検証

              SQLxは sqlx::query! マクロでコンパイル時にSQLを検証できます。

              準備(オフラインモード)

              # .envファイルを作成
              echo "DATABASE_URL=sqlite:./database.db" > .env
              
              # SQLx用のメタデータを生成
              cargo sqlx prepare
              

              使用例

              #![allow(unused)]
              fn main() {
              // コンパイル時にSQLが検証される
              let user = sqlx::query!(
                  "SELECT id, name, email FROM users WHERE id = ?",
                  id
              )
              .fetch_optional(&pool)
              .await?;
              
              // 存在しないカラムはコンパイルエラー
              // let user = sqlx::query!(
              //     "SELECT invalid_column FROM users"  // コンパイルエラー!
              // );
              }

              Axumとの統合

              プロジェクト構造

              src/
              ├── main.rs
              ├── db.rs        # データベース関連
              ├── models.rs    # データモデル
              └── handlers.rs  # APIハンドラー
              

              db.rs

              #![allow(unused)]
              fn main() {
              use sqlx::sqlite::SqlitePool;
              
              pub async fn create_pool(database_url: &str) -> Result<SqlitePool, sqlx::Error> {
                  SqlitePool::connect(database_url).await
              }
              }

              models.rs

              #![allow(unused)]
              fn main() {
              use serde::{Deserialize, Serialize};
              use sqlx::FromRow;
              
              #[derive(Debug, Clone, Serialize, FromRow)]
              pub struct User {
                  pub id: i64,
                  pub name: String,
                  pub email: String,
              }
              
              #[derive(Debug, Deserialize)]
              pub struct CreateUser {
                  pub name: String,
                  pub email: String,
              }
              
              #[derive(Debug, Deserialize)]
              pub struct UpdateUser {
                  pub name: Option<String>,
                  pub email: Option<String>,
              }
              }

              handlers.rs

              #![allow(unused)]
              fn main() {
              use axum::{
                  extract::{Path, State},
                  http::StatusCode,
                  Json,
              };
              use sqlx::sqlite::SqlitePool;
              
              use crate::models::{CreateUser, UpdateUser, User};
              
              type Pool = SqlitePool;
              
              // ユーザー一覧
              pub async fn list_users(
                  State(pool): State<Pool>,
              ) -> Result<Json<Vec<User>>, StatusCode> {
                  let users = sqlx::query_as::<_, User>("SELECT id, name, email FROM users")
                      .fetch_all(&pool)
                      .await
                      .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
              
                  Ok(Json(users))
              }
              
              // ユーザー取得
              pub async fn get_user(
                  State(pool): State<Pool>,
                  Path(id): Path<i64>,
              ) -> Result<Json<User>, StatusCode> {
                  let user = sqlx::query_as::<_, User>(
                      "SELECT id, name, email FROM users WHERE id = ?"
                  )
                  .bind(id)
                  .fetch_optional(&pool)
                  .await
                  .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
                  .ok_or(StatusCode::NOT_FOUND)?;
              
                  Ok(Json(user))
              }
              
              // ユーザー作成
              pub async fn create_user(
                  State(pool): State<Pool>,
                  Json(payload): Json<CreateUser>,
              ) -> Result<(StatusCode, Json<User>), StatusCode> {
                  let result = sqlx::query(
                      "INSERT INTO users (name, email) VALUES (?, ?)"
                  )
                  .bind(&payload.name)
                  .bind(&payload.email)
                  .execute(&pool)
                  .await
                  .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
              
                  let user = User {
                      id: result.last_insert_rowid(),
                      name: payload.name,
                      email: payload.email,
                  };
              
                  Ok((StatusCode::CREATED, Json(user)))
              }
              
              // ユーザー削除
              pub async fn delete_user(
                  State(pool): State<Pool>,
                  Path(id): Path<i64>,
              ) -> Result<StatusCode, StatusCode> {
                  let result = sqlx::query("DELETE FROM users WHERE id = ?")
                      .bind(id)
                      .execute(&pool)
                      .await
                      .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
              
                  if result.rows_affected() > 0 {
                      Ok(StatusCode::NO_CONTENT)
                  } else {
                      Err(StatusCode::NOT_FOUND)
                  }
              }
              }

              main.rs

              mod db;
              mod handlers;
              mod models;
              
              use axum::{routing::{get, post, delete}, Router};
              
              #[tokio::main]
              async fn main() {
                  let database_url = std::env::var("DATABASE_URL")
                      .unwrap_or_else(|_| "sqlite:./database.db".to_string());
              
                  let pool = db::create_pool(&database_url)
                      .await
                      .expect("Failed to create pool");
              
                  // マイグレーションを実行
                  sqlx::migrate!()
                      .run(&pool)
                      .await
                      .expect("Failed to run migrations");
              
                  let app = Router::new()
                      .route("/users", get(handlers::list_users).post(handlers::create_user))
                      .route("/users/:id", get(handlers::get_user).delete(handlers::delete_user))
                      .with_state(pool);
              
                  let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
                  println!("Server running on http://localhost:3000");
                  axum::serve(listener, app).await.unwrap();
              }

              トランザクション

              #![allow(unused)]
              fn main() {
              use sqlx::sqlite::SqlitePool;
              
              async fn transfer_funds(
                  pool: &SqlitePool,
                  from_id: i64,
                  to_id: i64,
                  amount: i64,
              ) -> Result<(), sqlx::Error> {
                  // トランザクション開始
                  let mut tx = pool.begin().await?;
              
                  // 送金元から減額
                  sqlx::query("UPDATE accounts SET balance = balance - ? WHERE id = ?")
                      .bind(amount)
                      .bind(from_id)
                      .execute(&mut *tx)
                      .await?;
              
                  // 送金先に加算
                  sqlx::query("UPDATE accounts SET balance = balance + ? WHERE id = ?")
                      .bind(amount)
                      .bind(to_id)
                      .execute(&mut *tx)
                      .await?;
              
                  // コミット
                  tx.commit().await?;
              
                  Ok(())
              }
              }

              エラーハンドリング

              #![allow(unused)]
              fn main() {
              use sqlx::Error as SqlxError;
              use axum::http::StatusCode;
              
              fn map_db_error(err: SqlxError) -> StatusCode {
                  match err {
                      SqlxError::RowNotFound => StatusCode::NOT_FOUND,
                      SqlxError::Database(db_err) => {
                          // ユニーク制約違反など
                          if db_err.is_unique_violation() {
                              StatusCode::CONFLICT
                          } else {
                              StatusCode::INTERNAL_SERVER_ERROR
                          }
                      }
                      _ => StatusCode::INTERNAL_SERVER_ERROR,
                  }
              }
              }

              まとめ

              操作メソッド
              INSERT/UPDATE/DELETEquery().execute()
              SELECT(単一)query_as().fetch_one() / fetch_optional()
              SELECT(複数)query_as().fetch_all()
              トランザクションpool.begin()tx.commit()
              コマンド用途
              sqlx database createDB作成
              sqlx migrate addマイグレーション作成
              sqlx migrate runマイグレーション実行
              cargo sqlx prepareオフラインモード準備

              確認テスト

              Q1. SQLxで複数の行を取得するメソッドは?

              Q2. SQLxでINSERT後に挿入されたIDを取得するには?

              Q3. fetch_optional(&pool).await? でIDが存在しない場合の戻り値は?

              Q4. 構造体が id, name のみで、SQLが "SELECT id, name, email FROM users" の場合の問題は?

              Q5. SQLxでブックマークを削除した後、削除されたかどうかを確認する正しい方法は?


              次のドキュメント: 05_authentication.md

              認証

              Webアプリケーションの認証の基礎を学びます。

              認証とは

              認証(Authentication): 「あなたは誰か」を確認すること 認可(Authorization): 「何ができるか」を確認すること

              ┌─────────────┐     認証      ┌─────────────┐      認可      ┌─────────────┐
              │   ユーザー   │  ───────────→ │  本人確認   │  ───────────→ │  権限確認   │
              │             │  ID/Password  │  OK/NG      │               │  許可/拒否  │
              └─────────────┘               └─────────────┘               └─────────────┘
              

              認証方式の種類

              1. セッションベース認証

              ┌────────┐          ┌────────────┐          ┌────────┐
              │ Client │ ──Login──→│   Server   │ ←───────→ │ Session│
              │        │ ←Cookie── │            │          │ Store  │
              │        │ ──Cookie─→│  検証      │          │        │
              └────────┘          └────────────┘          └────────┘
              

              特徴:

              • サーバーがセッション情報を保持
              • Cookieでセッション IDを送受信
              • 状態を持つ(ステートフル)

              2. トークンベース認証(JWT)

              ┌────────┐          ┌────────────┐
              │ Client │ ──Login──→│   Server   │
              │        │ ←JWT───── │            │
              │        │ ──JWT────→│  検証      │  ← トークン自体に情報
              └────────┘          └────────────┘
              

              特徴:

              • トークン自体に情報を含む
              • サーバーは状態を持たない(ステートレス)
              • スケールしやすい

              セッションベース認証の実装

              Cargo.toml

              [dependencies]
              axum = "0.7"
              axum-extra = { version = "0.9", features = ["cookie"] }
              tokio = { version = "1", features = ["full"] }
              tower-sessions = "0.12"
              tower-sessions-sqlx-store = { version = "0.12", features = ["sqlite"] }
              sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] }
              serde = { version = "1.0", features = ["derive"] }
              uuid = { version = "1", features = ["v4"] }
              argon2 = "0.5"  # パスワードハッシュ
              

              パスワードのハッシュ化

              絶対にパスワードを平文で保存しない!

              #![allow(unused)]
              fn main() {
              use argon2::{
                  password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
                  Argon2,
              };
              
              // パスワードをハッシュ化
              fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
                  let salt = SaltString::generate(&mut OsRng);
                  let argon2 = Argon2::default();
                  let hash = argon2.hash_password(password.as_bytes(), &salt)?;
                  Ok(hash.to_string())
              }
              
              // パスワードを検証
              fn verify_password(password: &str, hash: &str) -> bool {
                  let parsed_hash = match PasswordHash::new(hash) {
                      Ok(h) => h,
                      Err(_) => return false,
                  };
              
                  Argon2::default()
                      .verify_password(password.as_bytes(), &parsed_hash)
                      .is_ok()
              }
              }

              セッション管理

              #![allow(unused)]
              fn main() {
              use axum::{
                  extract::State,
                  http::StatusCode,
                  response::IntoResponse,
                  routing::{get, post},
                  Json, Router,
              };
              use serde::{Deserialize, Serialize};
              use tower_sessions::{Session, SessionManagerLayer};
              use tower_sessions_sqlx_store::SqliteStore;
              
              const USER_ID_KEY: &str = "user_id";
              
              #[derive(Deserialize)]
              struct LoginRequest {
                  email: String,
                  password: String,
              }
              
              #[derive(Serialize)]
              struct UserResponse {
                  id: i64,
                  email: String,
              }
              
              // ログイン
              async fn login(
                  session: Session,
                  State(pool): State<SqlitePool>,
                  Json(payload): Json<LoginRequest>,
              ) -> Result<Json<UserResponse>, StatusCode> {
                  // ユーザーを検索
                  let user = sqlx::query_as::<_, User>(
                      "SELECT id, email, password_hash FROM users WHERE email = ?"
                  )
                  .bind(&payload.email)
                  .fetch_optional(&pool)
                  .await
                  .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
                  .ok_or(StatusCode::UNAUTHORIZED)?;
              
                  // パスワードを検証
                  if !verify_password(&payload.password, &user.password_hash) {
                      return Err(StatusCode::UNAUTHORIZED);
                  }
              
                  // セッションにユーザーIDを保存
                  session
                      .insert(USER_ID_KEY, user.id)
                      .await
                      .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
              
                  Ok(Json(UserResponse {
                      id: user.id,
                      email: user.email,
                  }))
              }
              
              // ログアウト
              async fn logout(session: Session) -> StatusCode {
                  session.delete().await;
                  StatusCode::OK
              }
              
              // 現在のユーザーを取得
              async fn current_user(
                  session: Session,
                  State(pool): State<SqlitePool>,
              ) -> Result<Json<UserResponse>, StatusCode> {
                  // セッションからユーザーIDを取得
                  let user_id: i64 = session
                      .get(USER_ID_KEY)
                      .await
                      .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
                      .ok_or(StatusCode::UNAUTHORIZED)?;
              
                  // ユーザー情報を取得
                  let user = sqlx::query_as::<_, User>(
                      "SELECT id, email FROM users WHERE id = ?"
                  )
                  .bind(user_id)
                  .fetch_optional(&pool)
                  .await
                  .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
                  .ok_or(StatusCode::NOT_FOUND)?;
              
                  Ok(Json(UserResponse {
                      id: user.id,
                      email: user.email,
                  }))
              }
              }

              メイン関数

              use sqlx::sqlite::SqlitePool;
              use tower_sessions::SessionManagerLayer;
              use tower_sessions_sqlx_store::SqliteStore;
              
              #[tokio::main]
              async fn main() {
                  let pool = SqlitePool::connect("sqlite:./database.db").await.unwrap();
              
                  // セッションストアを作成
                  let session_store = SqliteStore::new(pool.clone());
                  session_store.migrate().await.unwrap();
              
                  let session_layer = SessionManagerLayer::new(session_store)
                      .with_secure(false)  // 開発環境用(本番ではtrue)
                      .with_http_only(true);
              
                  let app = Router::new()
                      .route("/login", post(login))
                      .route("/logout", post(logout))
                      .route("/me", get(current_user))
                      .layer(session_layer)
                      .with_state(pool);
              
                  let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
                  axum::serve(listener, app).await.unwrap();
              }

              認証ミドルウェア

              認証が必要なルートを保護するミドルウェアを作成します。

              #![allow(unused)]
              fn main() {
              use axum::{
                  extract::Request,
                  http::StatusCode,
                  middleware::Next,
                  response::Response,
              };
              use tower_sessions::Session;
              
              // 認証チェックミドルウェア
              async fn require_auth(
                  session: Session,
                  request: Request,
                  next: Next,
              ) -> Result<Response, StatusCode> {
                  // セッションにユーザーIDがあるかチェック
                  let user_id: Option<i64> = session
                      .get(USER_ID_KEY)
                      .await
                      .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
              
                  if user_id.is_none() {
                      return Err(StatusCode::UNAUTHORIZED);
                  }
              
                  // 次のハンドラーへ
                  Ok(next.run(request).await)
              }
              }

              ミドルウェアの適用

              #![allow(unused)]
              fn main() {
              use axum::middleware;
              
              let app = Router::new()
                  // 認証不要なルート
                  .route("/login", post(login))
                  .route("/register", post(register))
              
                  // 認証が必要なルート
                  .route("/me", get(current_user))
                  .route("/bookmarks", get(list_bookmarks).post(create_bookmark))
                  .route_layer(middleware::from_fn(require_auth))
              
                  .layer(session_layer)
                  .with_state(pool);
              }

              JWT認証(参考)

              JWTを使った認証の概要です。

              JWTの構造

              ヘッダー.ペイロード.署名
              
              eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
              eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
              SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
              

              Cargo.toml

              [dependencies]
              jsonwebtoken = "9"
              

              JWT生成・検証

              #![allow(unused)]
              fn main() {
              use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
              use serde::{Deserialize, Serialize};
              
              #[derive(Debug, Serialize, Deserialize)]
              struct Claims {
                  sub: String,  // ユーザーID
                  exp: usize,   // 有効期限
              }
              
              const SECRET: &[u8] = b"your-secret-key";  // 本番では環境変数から
              
              // JWT生成
              fn create_token(user_id: i64) -> Result<String, jsonwebtoken::errors::Error> {
                  let expiration = chrono::Utc::now()
                      .checked_add_signed(chrono::Duration::hours(24))
                      .unwrap()
                      .timestamp() as usize;
              
                  let claims = Claims {
                      sub: user_id.to_string(),
                      exp: expiration,
                  };
              
                  encode(
                      &Header::default(),
                      &claims,
                      &EncodingKey::from_secret(SECRET),
                  )
              }
              
              // JWT検証
              fn verify_token(token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
                  let token_data = decode::<Claims>(
                      token,
                      &DecodingKey::from_secret(SECRET),
                      &Validation::default(),
                  )?;
              
                  Ok(token_data.claims)
              }
              }

              セキュリティのベストプラクティス

              1. パスワード

              • 平文保存禁止: 必ずハッシュ化(argon2, bcrypt)
              • 強度要件: 最低8文字、複雑さの要件
              • レート制限: ブルートフォース攻撃対策

              2. セッション

              • HttpOnly: JavaScriptからアクセス禁止
              • Secure: HTTPS必須(本番環境)
              • SameSite: CSRF対策
              #![allow(unused)]
              fn main() {
              let session_layer = SessionManagerLayer::new(session_store)
                  .with_http_only(true)
                  .with_secure(true)       // 本番環境
                  .with_same_site(SameSite::Strict);
              }

              3. 一般的な対策

              • HTTPS必須(本番環境)
              • CORS設定を適切に
              • 入力値のバリデーション
              • エラーメッセージに詳細を含めない

              まとめ

              方式特徴用途
              セッションステートフル、サーバー側で管理従来型Webアプリ
              JWTステートレス、トークンに情報含むAPI、マイクロサービス
              セキュリティ対策
              パスワードのハッシュ化(argon2)
              セッションのHttpOnly/Secure設定
              HTTPS必須(本番)
              入力値のバリデーション

              確認テスト

              Q1. パスワードを保存する際に正しい方法は?

              Q2. セッションCookieのHttpOnly属性の目的は?

              Q3. ログイン処理で、ユーザーが見つからない場合とパスワードが間違っている場合のレスポンスとして適切なのは?

              Q4. 以下のコードの問題は?
              sqlx::query("INSERT INTO users (email, password) VALUES (?, ?)").bind(&email).bind(&password)

              Q5. JWTとセッションベース認証の違いとして正しいのは?


              次のドキュメント: 06_frontend_integration.md

              フロントエンド連携

              バックエンドAPIとフロントエンドの連携方法を学びます。

              連携パターン

              1. 静的ファイル配信 + API

              ┌────────────────────────────────────────────┐
              │                  Axumサーバー                │
              │  ┌──────────────┐    ┌──────────────┐     │
              │  │ 静的ファイル   │    │   API        │     │
              │  │ /static/*    │    │   /api/*     │     │
              │  │ HTML/JS/CSS  │    │   JSON       │     │
              │  └──────────────┘    └──────────────┘     │
              └────────────────────────────────────────────┘
              

              2. 分離デプロイ

              ┌──────────────┐         ┌──────────────┐
              │ フロントエンド │  CORS   │  APIサーバー  │
              │ (Vercelなど)  │ ←─────→ │   (Rust)     │
              └──────────────┘         └──────────────┘
              

              静的ファイルの配信

              Cargo.toml

              [dependencies]
              tower-http = { version = "0.5", features = ["fs", "cors"] }
              

              ディレクトリ構造

              project/
              ├── src/
              │   └── main.rs
              ├── static/
              │   ├── index.html
              │   ├── style.css
              │   └── app.js
              └── Cargo.toml
              

              静的ファイルの配信設定

              use axum::{routing::get, Router};
              use tower_http::services::ServeDir;
              
              #[tokio::main]
              async fn main() {
                  let app = Router::new()
                      // API ルート
                      .route("/api/users", get(list_users))
              
                      // 静的ファイル配信
                      .nest_service("/", ServeDir::new("static"));
              
                  let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
                  axum::serve(listener, app).await.unwrap();
              }

              index.html の例

              <!DOCTYPE html>
              <html lang="ja">
              <head>
                  <meta charset="UTF-8">
                  <meta name="viewport" content="width=device-width, initial-scale=1.0">
                  <title>My App</title>
                  <link rel="stylesheet" href="/style.css">
              </head>
              <body>
                  <div id="app">
                      <h1>ブックマーク管理</h1>
                      <ul id="bookmarks"></ul>
                      <form id="add-form">
                          <input type="text" name="url" placeholder="URL" required>
                          <input type="text" name="title" placeholder="タイトル" required>
                          <button type="submit">追加</button>
                      </form>
                  </div>
                  <script src="/app.js"></script>
              </body>
              </html>
              

              JavaScript (Fetch API)

              // app.js
              
              // ブックマーク一覧を取得
              async function fetchBookmarks() {
                  const response = await fetch('/api/bookmarks');
                  const bookmarks = await response.json();
              
                  const ul = document.getElementById('bookmarks');
                  ul.innerHTML = '';
              
                  bookmarks.forEach(bookmark => {
                      const li = document.createElement('li');
                      li.innerHTML = `
                          <a href="${bookmark.url}" target="_blank">${bookmark.title}</a>
                          <button onclick="deleteBookmark(${bookmark.id})">削除</button>
                      `;
                      ul.appendChild(li);
                  });
              }
              
              // ブックマークを追加
              async function addBookmark(url, title) {
                  const response = await fetch('/api/bookmarks', {
                      method: 'POST',
                      headers: {
                          'Content-Type': 'application/json',
                      },
                      body: JSON.stringify({ url, title }),
                  });
              
                  if (response.ok) {
                      fetchBookmarks();  // 一覧を更新
                  }
              }
              
              // ブックマークを削除
              async function deleteBookmark(id) {
                  const response = await fetch(`/api/bookmarks/${id}`, {
                      method: 'DELETE',
                  });
              
                  if (response.ok) {
                      fetchBookmarks();  // 一覧を更新
                  }
              }
              
              // フォーム送信
              document.getElementById('add-form').addEventListener('submit', async (e) => {
                  e.preventDefault();
                  const formData = new FormData(e.target);
                  await addBookmark(formData.get('url'), formData.get('title'));
                  e.target.reset();
              });
              
              // 初期読み込み
              fetchBookmarks();
              

              CORS(Cross-Origin Resource Sharing)

              フロントエンドとバックエンドが異なるオリジンの場合、CORSの設定が必要です。

              なぜCORSが必要か

              フロントエンド: https://frontend.example.com
              バックエンド:   https://api.example.com
              
              → オリジンが異なるため、ブラウザがリクエストをブロック
              → CORS設定で許可する
              

              CORS設定

              use axum::{routing::get, Router};
              use tower_http::cors::{CorsLayer, Any};
              use http::Method;
              
              #[tokio::main]
              async fn main() {
                  // CORS設定
                  let cors = CorsLayer::new()
                      .allow_origin(Any)  // すべてのオリジンを許可(開発用)
                      .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
                      .allow_headers(Any);
              
                  let app = Router::new()
                      .route("/api/bookmarks", get(list_bookmarks).post(create_bookmark))
                      .layer(cors);
              
                  // ...
              }

              本番環境のCORS設定

              #![allow(unused)]
              fn main() {
              use tower_http::cors::{CorsLayer, AllowOrigin};
              use http::{Method, HeaderValue};
              
              let cors = CorsLayer::new()
                  .allow_origin(AllowOrigin::exact(
                      "https://your-frontend.com".parse().unwrap()
                  ))
                  .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
                  .allow_headers([
                      http::header::CONTENT_TYPE,
                      http::header::AUTHORIZATION,
                  ])
                  .allow_credentials(true);  // Cookieを使う場合
              }

              htmx によるシンプルな連携

              htmxはJavaScriptをほぼ書かずにAjaxを実現できるライブラリです。

              htmxの基本

              <!DOCTYPE html>
              <html lang="ja">
              <head>
                  <script src="https://unpkg.com/htmx.org@1.9.10"></script>
              </head>
              <body>
                  <!-- クリックでGETリクエスト -->
                  <button hx-get="/api/hello" hx-target="#result">
                      挨拶を取得
                  </button>
              
                  <!-- 結果を表示 -->
                  <div id="result"></div>
              </body>
              </html>
              

              htmxの主な属性

              属性説明
              hx-getGETリクエスト
              hx-postPOSTリクエスト
              hx-putPUTリクエスト
              hx-deleteDELETEリクエスト
              hx-target結果を挿入する要素
              hx-swap挿入方法(innerHTML, outerHTML等)
              hx-triggerトリガーイベント

              htmxでのCRUD例

              <!-- ブックマーク一覧 -->
              <div id="bookmarks" hx-get="/api/bookmarks" hx-trigger="load">
                  <!-- サーバーからHTMLが挿入される -->
              </div>
              
              <!-- 追加フォーム -->
              <form hx-post="/api/bookmarks"
                    hx-target="#bookmarks"
                    hx-swap="beforeend">
                  <input type="text" name="url" placeholder="URL" required>
                  <input type="text" name="title" placeholder="タイトル" required>
                  <button type="submit">追加</button>
              </form>
              

              サーバー側(HTMLを返す)

              #![allow(unused)]
              fn main() {
              use axum::response::Html;
              
              // htmx用:HTMLフラグメントを返す
              async fn list_bookmarks_html(State(pool): State<SqlitePool>) -> Html<String> {
                  let bookmarks = sqlx::query_as::<_, Bookmark>("SELECT * FROM bookmarks")
                      .fetch_all(&pool)
                      .await
                      .unwrap_or_default();
              
                  let html = bookmarks
                      .iter()
                      .map(|b| format!(
                          r#"<li>
                              <a href="{}">{}</a>
                              <button hx-delete="/api/bookmarks/{}"
                                      hx-target="closest li"
                                      hx-swap="outerHTML">削除</button>
                          </li>"#,
                          b.url, b.title, b.id
                      ))
                      .collect::<Vec<_>>()
                      .join("\n");
              
                  Html(html)
              }
              
              // 追加後は新しいリストアイテムを返す
              async fn create_bookmark_html(
                  State(pool): State<SqlitePool>,
                  Form(payload): Form<CreateBookmark>,
              ) -> Html<String> {
                  // ... DBに保存 ...
              
                  Html(format!(
                      r#"<li>
                          <a href="{}">{}</a>
                          <button hx-delete="/api/bookmarks/{}"
                                  hx-target="closest li"
                                  hx-swap="outerHTML">削除</button>
                      </li>"#,
                      payload.url, payload.title, id
                  ))
              }
              }

              環境変数による設定

              .env ファイル

              DATABASE_URL=sqlite:./database.db
              FRONTEND_URL=http://localhost:5173
              

              環境変数の読み込み

              use std::env;
              
              fn main() {
                  // .envファイルを読み込み(開発用)
                  dotenv::dotenv().ok();
              
                  let database_url = env::var("DATABASE_URL")
                      .expect("DATABASE_URL must be set");
              
                  let frontend_url = env::var("FRONTEND_URL")
                      .unwrap_or_else(|_| "http://localhost:3000".to_string());
              
                  // ...
              }

              APIレスポンスの形式

              成功時

              {
                "data": {
                  "id": 1,
                  "url": "https://example.com",
                  "title": "Example"
                }
              }
              

              エラー時

              {
                "error": {
                  "code": "NOT_FOUND",
                  "message": "Bookmark not found"
                }
              }
              

              統一レスポンス型

              #![allow(unused)]
              fn main() {
              use serde::Serialize;
              
              #[derive(Serialize)]
              #[serde(untagged)]
              enum ApiResponse<T> {
                  Success { data: T },
                  Error { error: ApiError },
              }
              
              #[derive(Serialize)]
              struct ApiError {
                  code: String,
                  message: String,
              }
              
              // 使用例
              async fn get_bookmark(Path(id): Path<i64>) -> Json<ApiResponse<Bookmark>> {
                  match find_bookmark(id).await {
                      Ok(bookmark) => Json(ApiResponse::Success { data: bookmark }),
                      Err(_) => Json(ApiResponse::Error {
                          error: ApiError {
                              code: "NOT_FOUND".to_string(),
                              message: "Bookmark not found".to_string(),
                          }
                      }),
                  }
              }
              }

              まとめ

              方式特徴用途
              静的ファイル配信シンプル、同一オリジン小〜中規模アプリ
              分離デプロイ柔軟、スケーラブル大規模アプリ
              htmxJavaScript最小限シンプルなUI
              設定用途
              ServeDir静的ファイル配信
              CorsLayerCORS設定
              環境変数設定の外部化

              確認テスト

              Q1. CORSが必要になるのはどのような場合?

              Q2. Fetch APIで JSONを送信する際に必要なヘッダーは?

              Q3. htmxのhx-swap="outerHTML"の動作は?

              Q4. fetch APIでJSON送信時にContent-Typeヘッダーがない場合の問題は?

              Q5. allow_credentials(true)を使う場合のCORS設定で正しいものは?


              次のドキュメント: 07_deployment.md

              デプロイメント

              Rustアプリケーションのビルドとデプロイ方法を学びます。

              リリースビルド

              開発ビルド vs リリースビルド

              # 開発ビルド(デバッグ情報あり、最適化なし)
              cargo build
              
              # リリースビルド(最適化あり、高速)
              cargo build --release
              
              開発ビルドリリースビルド
              コマンドcargo buildcargo build --release
              出力先target/debug/target/release/
              最適化なしあり
              ビルド時間短い長い
              実行速度遅い速い
              バイナリサイズ大きい小さい

              実行

              # 開発
              cargo run
              
              # リリース
              cargo run --release
              
              # または直接バイナリを実行
              ./target/release/my-app
              

              バイナリの最適化

              Cargo.toml の設定

              [profile.release]
              # 最適化レベル(0-3、sはサイズ優先、zは最小サイズ)
              opt-level = 3
              
              # リンク時最適化(LTO)
              lto = true
              
              # コード生成ユニット(1が最も最適化される)
              codegen-units = 1
              
              # パニック時の動作(abort = バイナリ小さくなる)
              panic = "abort"
              
              # デバッグシンボルを削除
              strip = true
              

              バイナリサイズの確認

              # サイズ確認
              ls -lh target/release/my-app
              
              # さらに小さくする(stripコマンド)
              strip target/release/my-app
              

              環境変数と設定

              環境変数の読み込み

              #![allow(unused)]
              fn main() {
              use std::env;
              
              struct Config {
                  database_url: String,
                  port: u16,
                  environment: String,
              }
              
              impl Config {
                  fn from_env() -> Self {
                      Self {
                          database_url: env::var("DATABASE_URL")
                              .expect("DATABASE_URL must be set"),
                          port: env::var("PORT")
                              .unwrap_or_else(|_| "3000".to_string())
                              .parse()
                              .expect("PORT must be a number"),
                          environment: env::var("RUST_ENV")
                              .unwrap_or_else(|_| "development".to_string()),
                      }
                  }
              }
              }

              .env ファイル(開発用)

              # .env
              DATABASE_URL=sqlite:./database.db
              PORT=3000
              RUST_ENV=development
              
              // dotenvy クレートを使用
              fn main() {
                  dotenvy::dotenv().ok();  // .envファイルを読み込み
                  let config = Config::from_env();
                  // ...
              }

              Docker

              Dockerfile(マルチステージビルド)

              # ビルドステージ
              FROM rust:1.75 AS builder
              
              WORKDIR /app
              
              # 依存関係をキャッシュするため、先にCargo.tomlだけコピー
              COPY Cargo.toml Cargo.lock ./
              RUN mkdir src && echo "fn main() {}" > src/main.rs
              RUN cargo build --release
              RUN rm -rf src
              
              # ソースコードをコピーしてビルド
              COPY . .
              RUN touch src/main.rs  # タイムスタンプ更新
              RUN cargo build --release
              
              # 実行ステージ
              FROM debian:bookworm-slim
              
              # 必要なライブラリをインストール
              RUN apt-get update && apt-get install -y \
                  ca-certificates \
                  libssl3 \
                  && rm -rf /var/lib/apt/lists/*
              
              # 非rootユーザーで実行
              RUN useradd -m appuser
              USER appuser
              
              WORKDIR /app
              
              # ビルド成果物をコピー
              COPY --from=builder /app/target/release/my-app /app/my-app
              
              # 静的ファイルをコピー(必要な場合)
              COPY --from=builder /app/static /app/static
              
              ENV PORT=3000
              EXPOSE 3000
              
              CMD ["./my-app"]
              

              .dockerignore

              target/
              .git/
              .env
              *.md
              Dockerfile
              .dockerignore
              

              Dockerコマンド

              # イメージのビルド
              docker build -t my-app .
              
              # コンテナの実行
              docker run -p 3000:3000 \
                -e DATABASE_URL=sqlite:/app/data/database.db \
                -v $(pwd)/data:/app/data \
                my-app
              
              # バックグラウンドで実行
              docker run -d -p 3000:3000 --name my-app my-app
              

              Docker Compose

              docker-compose.yml

              version: '3.8'
              
              services:
                app:
                  build: .
                  ports:
                    - "3000:3000"
                  environment:
                    - DATABASE_URL=sqlite:/app/data/database.db
                    - RUST_ENV=production
                  volumes:
                    - ./data:/app/data
                  restart: unless-stopped
              
                # PostgreSQLを使う場合
                # db:
                #   image: postgres:15
                #   environment:
                #     POSTGRES_USER: user
                #     POSTGRES_PASSWORD: password
                #     POSTGRES_DB: myapp
                #   volumes:
                #     - postgres_data:/var/lib/postgresql/data
              
              # volumes:
              #   postgres_data:
              

              Docker Composeコマンド

              # 起動
              docker-compose up
              
              # バックグラウンドで起動
              docker-compose up -d
              
              # 停止
              docker-compose down
              
              # ログ確認
              docker-compose logs -f app
              
              # 再ビルド
              docker-compose up --build
              

              クラウドデプロイ

              Railway

              # Railway CLIをインストール
              npm install -g @railway/cli
              
              # ログイン
              railway login
              
              # プロジェクト作成
              railway init
              
              # デプロイ
              railway up
              

              Fly.io

              # Fly CLIをインストール
              curl -L https://fly.io/install.sh | sh
              
              # ログイン
              fly auth login
              
              # アプリ作成
              fly launch
              
              # デプロイ
              fly deploy
              

              fly.toml

              app = "my-app"
              primary_region = "nrt"  # 東京
              
              [build]
              
              [http_service]
                internal_port = 3000
                force_https = true
              
              [env]
                RUST_ENV = "production"
              

              Shuttle(Rust専用)

              # Shuttle CLIをインストール
              cargo install cargo-shuttle
              
              # プロジェクト作成
              cargo shuttle init
              
              # ローカル実行
              cargo shuttle run
              
              # デプロイ
              cargo shuttle deploy
              

              ヘルスチェック

              ヘルスチェックエンドポイント

              #![allow(unused)]
              fn main() {
              async fn health_check() -> &'static str {
                  "OK"
              }
              
              // より詳細なチェック
              async fn health_detailed(State(pool): State<SqlitePool>) -> Json<HealthStatus> {
                  let db_ok = sqlx::query("SELECT 1")
                      .execute(&pool)
                      .await
                      .is_ok();
              
                  Json(HealthStatus {
                      status: if db_ok { "healthy" } else { "unhealthy" },
                      database: db_ok,
                      version: env!("CARGO_PKG_VERSION"),
                  })
              }
              
              #[derive(Serialize)]
              struct HealthStatus {
                  status: &'static str,
                  database: bool,
                  version: &'static str,
              }
              }

              ルートに追加

              #![allow(unused)]
              fn main() {
              let app = Router::new()
                  .route("/health", get(health_check))
                  .route("/health/detailed", get(health_detailed))
                  // ... 他のルート
              }

              ログ設定

              tracing クレート

              [dependencies]
              tracing = "0.1"
              tracing-subscriber = { version = "0.3", features = ["env-filter"] }
              
              use tracing::{info, warn, error};
              use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
              
              fn init_logging() {
                  tracing_subscriber::registry()
                      .with(
                          tracing_subscriber::EnvFilter::try_from_default_env()
                              .unwrap_or_else(|_| "my_app=debug,tower_http=debug".into()),
                      )
                      .with(tracing_subscriber::fmt::layer())
                      .init();
              }
              
              #[tokio::main]
              async fn main() {
                  init_logging();
              
                  info!("Starting server...");
              
                  // ...
              }

              リクエストログ

              #![allow(unused)]
              fn main() {
              use tower_http::trace::TraceLayer;
              
              let app = Router::new()
                  .route("/", get(handler))
                  .layer(TraceLayer::new_for_http());
              }

              デプロイチェックリスト

              セキュリティ

              • 環境変数でシークレットを管理
              • HTTPSを強制(本番)
              • CORSを適切に設定
              • SQLインジェクション対策(パラメータバインド)

              パフォーマンス

              • リリースビルドを使用
              • データベース接続プール設定
              • 適切なタイムアウト設定

              運用

              • ヘルスチェックエンドポイント
              • ログ出力の設定
              • エラーハンドリング
              • グレースフルシャットダウン

              グレースフルシャットダウン

              use tokio::signal;
              
              #[tokio::main]
              async fn main() {
                  let app = Router::new()
                      // ... routes ...
                      ;
              
                  let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
              
                  axum::serve(listener, app)
                      .with_graceful_shutdown(shutdown_signal())
                      .await
                      .unwrap();
              }
              
              async fn shutdown_signal() {
                  let ctrl_c = async {
                      signal::ctrl_c()
                          .await
                          .expect("Failed to install Ctrl+C handler");
                  };
              
                  #[cfg(unix)]
                  let terminate = async {
                      signal::unix::signal(signal::unix::SignalKind::terminate())
                          .expect("Failed to install signal handler")
                          .recv()
                          .await;
                  };
              
                  #[cfg(not(unix))]
                  let terminate = std::future::pending::<()>();
              
                  tokio::select! {
                      _ = ctrl_c => {},
                      _ = terminate => {},
                  }
              
                  println!("Shutting down gracefully...");
              }

              まとめ

              コマンド用途
              cargo build --releaseリリースビルド
              docker buildDockerイメージ作成
              docker-compose upDocker Compose起動
              デプロイ先特徴
              Railwayシンプル、自動デプロイ
              Fly.ioグローバル、高速
              ShuttleRust専用、簡単

              確認テスト

              Q1. cargo build --release の特徴として正しいものは?

              Q2. Dockerのマルチステージビルドを使う主な理由は?

              Q3. マルチステージビルドなしのDockerfileの主な問題点は?

              Q4. std::env::var("PORT")の戻り値の型は?

              Q5. ヘルスチェックでデータベース接続失敗時に返すべきHTTPステータスコードは?


              Phase 5 完了!

              おめでとうございます!Phase 5を完了しました。

              学んだこと:

              • HTTP/RESTの基礎
              • Axumによるウェブアプリケーション開発
              • SQLxによるデータベース操作
              • 認証の実装
              • フロントエンド連携
              • デプロイメント

              次は最終プロジェクトに挑戦しましょう!

              次のドキュメント: 最終プロジェクト

              よくあるエラーと対処法

              Rust学習中に遭遇しやすいエラーとその解決方法をまとめました。

              所有権関連のエラー

              E0382: value borrowed after move

              エラーメッセージ

              error[E0382]: borrow of moved value: `s`
               --> src/main.rs:4:20
                |
              2 |     let s = String::from("hello");
                |         - move occurs because `s` has type `String`
              3 |     let s2 = s;
                |              - value moved here
              4 |     println!("{}", s);
                |                    ^ value borrowed here after move
              

              原因: 値が別の変数にムーブされた後に使用しようとしている

              解決方法:

              #![allow(unused)]
              fn main() {
              // 方法1: クローンする
              let s = String::from("hello");
              let s2 = s.clone();
              println!("{}", s);  // OK
              
              // 方法2: 参照を使う
              let s = String::from("hello");
              let s2 = &s;
              println!("{}", s);  // OK
              }

              E0502: cannot borrow as mutable because it is also borrowed as immutable

              エラーメッセージ

              error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
               --> src/main.rs:4:5
                |
              3 |     let first = &v[0];
                |                  - immutable borrow occurs here
              4 |     v.push(4);
                |     ^^^^^^^^^ mutable borrow occurs here
              5 |     println!("{}", first);
                |                    ----- immutable borrow later used here
              

              原因: 不変参照がある間に可変操作をしようとしている

              解決方法:

              #![allow(unused)]
              fn main() {
              // 方法1: 不変参照を先に使い切る
              let mut v = vec![1, 2, 3];
              let first = &v[0];
              println!("{}", first);  // ここで使い切る
              v.push(4);              // その後で変更
              
              // 方法2: インデックスを保存する
              let mut v = vec![1, 2, 3];
              let first_idx = 0;
              v.push(4);
              println!("{}", v[first_idx]);
              }

              E0499: cannot borrow as mutable more than once

              エラーメッセージ

              error[E0499]: cannot borrow `x` as mutable more than once at a time
               --> src/main.rs:4:17
                |
              3 |     let r1 = &mut x;
                |              ------ first mutable borrow occurs here
              4 |     let r2 = &mut x;
                |              ^^^^^^ second mutable borrow occurs here
              5 |     println!("{}, {}", r1, r2);
                |                        -- first borrow later used here
              

              原因: 可変参照を同時に複数作ろうとしている

              解決方法:

              #![allow(unused)]
              fn main() {
              // 方法1: 順番に使う
              let mut x = 5;
              let r1 = &mut x;
              *r1 += 1;
              // r1のスコープ終了
              
              let r2 = &mut x;
              *r2 += 1;
              
              // 方法2: ブロックでスコープを分ける
              let mut x = 5;
              {
                  let r1 = &mut x;
                  *r1 += 1;
              }
              {
                  let r2 = &mut x;
                  *r2 += 1;
              }
              }

              ライフタイム関連のエラー

              E0106: missing lifetime specifier

              エラーメッセージ

              error[E0106]: missing lifetime specifier
               --> src/main.rs:1:33
                |
              1 | fn longest(x: &str, y: &str) -> &str {
                |               ----     ----     ^ expected named lifetime parameter
              

              原因: 複数の参照を受け取って参照を返す関数でライフタイムが不明

              解決方法:

              #![allow(unused)]
              fn main() {
              // ライフタイム注釈を追加
              fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
                  if x.len() > y.len() {
                      x
                  } else {
                      y
                  }
              }
              }

              E0597: borrowed value does not live long enough

              エラーメッセージ

              error[E0597]: `x` does not live long enough
               --> src/main.rs:4:9
                |
              3 |     let r;
                |         - borrow later stored here
              4 |         let x = 5;
              5 |         r = &x;
                |             ^^ borrowed value does not live long enough
              6 |     }
                |     - `x` dropped here while still borrowed
              

              原因: 参照先がスコープを抜けて無効になる

              解決方法:

              #![allow(unused)]
              fn main() {
              // 参照先を外側のスコープに移動
              let x = 5;
              let r = &x;
              println!("{}", r);
              }

              型関連のエラー

              E0308: mismatched types

              エラーメッセージ

              error[E0308]: mismatched types
               --> src/main.rs:2:18
                |
              2 |     let x: i32 = "hello";
                |            ---   ^^^^^^^ expected `i32`, found `&str`
                |            |
                |            expected due to this
              

              原因: 期待される型と実際の型が一致しない

              解決方法:

              #![allow(unused)]
              fn main() {
              // 正しい型を使う
              let x: i32 = 42;
              
              // または型注釈を省略
              let x = "hello";
              }

              E0277: the trait bound is not satisfied

              エラーメッセージ

              error[E0277]: the trait bound `MyStruct: std::fmt::Debug` is not satisfied
               --> src/main.rs:5:22
                |
              5 |     println!("{:?}", my_struct);
                |                      ^^^^^^^^^ `MyStruct` cannot be formatted using `{:?}`
              

              原因: 必要なトレイトが実装されていない

              解決方法:

              // deriveでトレイトを実装
              #[derive(Debug)]
              struct MyStruct {
                  value: i32,
              }
              
              fn main() {
                  let my_struct = MyStruct { value: 42 };
                  println!("{:?}", my_struct);  // OK
              }

              Option/Result関連のエラー

              cannot use ? in a function that returns ()

              エラーメッセージ

              error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option`
               --> src/main.rs:3:13
                |
              3 |     let f = File::open("file.txt")?;
                |             ^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
              

              原因: ?演算子はResultOptionを返す関数でしか使えない

              解決方法:

              // 方法1: 関数の戻り値をResultにする
              fn main() -> Result<(), std::io::Error> {
                  let f = File::open("file.txt")?;
                  Ok(())
              }
              
              // 方法2: matchでハンドリング
              fn main() {
                  match File::open("file.txt") {
                      Ok(f) => { /* 処理 */ },
                      Err(e) => eprintln!("Error: {}", e),
                  }
              }
              
              // 方法3: unwrap(エラー時はパニック)
              fn main() {
                  let f = File::open("file.txt").unwrap();
              }

              method unwrap not found

              エラーメッセージ

              error[E0599]: no method named `unwrap` found for type `i32`
              

              原因: unwrapOptionResultのメソッドで、普通の値には使えない

              解決方法:

              #![allow(unused)]
              fn main() {
              // Optionを返す関数の結果に使う
              let v = vec![1, 2, 3];
              let first = v.get(0).unwrap();  // get()はOption<&T>を返す
              }

              モジュール関連のエラー

              E0432: unresolved import

              エラーメッセージ

              error[E0432]: unresolved import `crate::models`
               --> src/main.rs:1:5
                |
              1 | use crate::models;
                |     ^^^^^^^^^^^^^ no `models` in the root
              

              原因: モジュールが正しく宣言されていない

              解決方法:

              #![allow(unused)]
              fn main() {
              // main.rs または lib.rs でモジュールを宣言
              mod models;  // これを追加
              
              use crate::models::User;
              }

              ファイル構造:

              src/
              ├── main.rs     # mod models; を宣言
              └── models.rs   # または models/mod.rs
              

              E0603: function is private

              エラーメッセージ

              error[E0603]: function `internal_function` is private
               --> src/main.rs:3:14
                |
              3 | my_module::internal_function();
                |            ^^^^^^^^^^^^^^^^^ private function
              

              原因: 非公開の関数にアクセスしようとしている

              解決方法:

              #![allow(unused)]
              fn main() {
              // 関数をpubにする
              pub fn internal_function() {
                  // ...
              }
              }

              非同期関連のエラー

              future cannot be sent between threads safely

              エラーメッセージ

              error: future cannot be sent between threads safely
              

              原因: 非同期タスク内でSendでない値を使っている

              解決方法:

              #![allow(unused)]
              fn main() {
              // 方法1: Arcでラップ
              use std::sync::Arc;
              let data = Arc::new(data);
              
              // 方法2: 値をクローンしてタスクに渡す
              let data = data.clone();
              tokio::spawn(async move {
                  // data を使用
              });
              }

              async block/function expected but found fn

              エラーメッセージ

              error: `await` is only allowed inside `async` functions and blocks
              

              原因: asyncでない関数内でawaitを使っている

              解決方法:

              #![allow(unused)]
              fn main() {
              // 関数をasyncにする
              async fn my_function() {
                  let result = some_async_fn().await;
              }
              }

              コンパイル時のヒント

              未使用の変数警告

              warning: unused variable: `x`
               --> src/main.rs:2:9
                |
              2 |     let x = 5;
                |         ^ help: if this is intentional, prefix it with an underscore: `_x`
              

              解決方法:

              #![allow(unused)]
              fn main() {
              // 使う予定がない場合はアンダースコアを付ける
              let _x = 5;
              
              // または完全に無視
              let _ = some_function();
              }

              デバッグのコツ

              1. 型を確認する

              #![allow(unused)]
              fn main() {
              // コンパイラに型を教えてもらう
              let x: () = some_expression;  // エラーメッセージで型がわかる
              }

              2. 中間値を出力する

              #![allow(unused)]
              fn main() {
              // dbg!マクロを使う
              let result = dbg!(some_function());
              // 出力: [src/main.rs:2] some_function() = 42
              }

              3. コンパイラの提案を読む

              help: consider borrowing here: `&v`
              

              Rustコンパイラは多くの場合、修正方法を提案してくれます。help:の行を注意深く読みましょう。


              まとめ

              エラー種別主な原因対処法
              E0382ムーブ後の使用clone()または参照を使う
              E0502借用ルール違反参照の使用順序を変える
              E0106ライフタイム不明ライフタイム注釈を追加
              E0308型の不一致正しい型を使う
              E0277トレイト未実装deriveまたは手動実装

              エラーメッセージを落ち着いて読み、コンパイラの提案に従うことが大切です。

              文字列型変換ガイド

              Rustの文字列型の変換方法をまとめました。

              文字列型の種類

              説明所有権
              Stringヒープ上の可変文字列あり
              &str文字列スライス(参照)なし
              &StringStringへの参照なし
              &'static str静的な文字列リテラルなし

              変換早見表

                                  .to_string()
                            ┌────────────────────┐
                            │                    ▼
                         &str ◄──────────────── String
                            │    &s, .as_str()    │
                            │                     │
                            │                     │
                  .to_owned()                  .clone()
                            │                     │
                            ▼                     ▼
                         String               String
              

              &str → String

              #![allow(unused)]
              fn main() {
              // 方法1: to_string()
              let s: &str = "hello";
              let string: String = s.to_string();
              
              // 方法2: to_owned()
              let string: String = s.to_owned();
              
              // 方法3: String::from()
              let string: String = String::from(s);
              
              // 方法4: into()
              let string: String = s.into();
              }

              どれを使うべき?

              • to_string(): 最も一般的
              • to_owned(): 「所有権を得る」という意図が明確
              • String::from(): 型変換であることが明確
              • into(): ジェネリクスで便利

              String → &str

              #![allow(unused)]
              fn main() {
              let s: String = String::from("hello");
              
              // 方法1: &演算子(自動deref)
              let slice: &str = &s;
              
              // 方法2: as_str()
              let slice: &str = s.as_str();
              
              // 方法3: スライス構文
              let slice: &str = &s[..];
              }

              どれを使うべき?

              • &s: 最もシンプル
              • as_str(): 意図が明確

              数値 → String

              #![allow(unused)]
              fn main() {
              // 方法1: to_string()
              let n: i32 = 42;
              let s: String = n.to_string();
              
              // 方法2: format!マクロ
              let s: String = format!("{}", n);
              
              // フォーマット指定
              let hex: String = format!("{:x}", 255);   // "ff"
              let padded: String = format!("{:05}", 42); // "00042"
              }

              String → 数値

              #![allow(unused)]
              fn main() {
              let s: &str = "42";
              
              // 方法1: parse()
              let n: i32 = s.parse().unwrap();
              
              // 方法2: parse()(型を明示)
              let n = s.parse::<i32>().unwrap();
              
              // エラーハンドリング
              match s.parse::<i32>() {
                  Ok(n) => println!("数値: {}", n),
                  Err(e) => println!("パースエラー: {}", e),
              }
              }

              char → String

              #![allow(unused)]
              fn main() {
              let c: char = 'A';
              
              // 方法1: to_string()
              let s: String = c.to_string();
              
              // 方法2: String::from()
              let s: String = String::from(c);
              
              // 方法3: format!
              let s: String = format!("{}", c);
              }

              String → char

              #![allow(unused)]
              fn main() {
              let s: String = String::from("A");
              
              // 最初の文字を取得
              let c: char = s.chars().next().unwrap();
              
              // すべての文字をイテレート
              for c in s.chars() {
                  println!("{}", c);
              }
              }

              Vec ↔ String

              #![allow(unused)]
              fn main() {
              // String → Vec<u8>
              let s = String::from("hello");
              let bytes: Vec<u8> = s.into_bytes();
              
              // Vec<u8> → String(UTF-8として解釈)
              let bytes = vec![104, 101, 108, 108, 111];  // "hello"
              let s: String = String::from_utf8(bytes).unwrap();
              
              // 失敗する可能性がある場合
              match String::from_utf8(bytes) {
                  Ok(s) => println!("{}", s),
                  Err(e) => println!("Invalid UTF-8: {}", e),
              }
              
              // lossy変換(無効なバイトは置換)
              let s = String::from_utf8_lossy(&bytes);
              }

              &[u8] → &str

              #![allow(unused)]
              fn main() {
              let bytes: &[u8] = b"hello";
              
              // UTF-8として解釈
              let s: &str = std::str::from_utf8(bytes).unwrap();
              
              // または
              match std::str::from_utf8(bytes) {
                  Ok(s) => println!("{}", s),
                  Err(e) => println!("Invalid UTF-8: {}", e),
              }
              }

              PathBuf ↔ String

              #![allow(unused)]
              fn main() {
              use std::path::PathBuf;
              
              // String → PathBuf
              let s = String::from("/path/to/file");
              let path = PathBuf::from(&s);
              
              // PathBuf → String
              let path = PathBuf::from("/path/to/file");
              let s: String = path.to_string_lossy().into_owned();
              
              // PathBuf → &str(失敗する可能性あり)
              if let Some(s) = path.to_str() {
                  println!("{}", s);
              }
              }

              OsString ↔ String

              #![allow(unused)]
              fn main() {
              use std::ffi::OsString;
              
              // String → OsString
              let s = String::from("hello");
              let os_string = OsString::from(&s);
              
              // OsString → String
              let os_string = OsString::from("hello");
              match os_string.into_string() {
                  Ok(s) => println!("{}", s),
                  Err(os_string) => println!("Invalid UTF-8"),
              }
              
              // lossy変換
              let s = os_string.to_string_lossy().into_owned();
              }

              CString ↔ String(FFI用)

              #![allow(unused)]
              fn main() {
              use std::ffi::{CString, CStr};
              
              // String → CString
              let s = String::from("hello");
              let c_string = CString::new(s).unwrap();  // NULLバイトがあるとエラー
              
              // CString → String
              let c_string = CString::new("hello").unwrap();
              let s: String = c_string.into_string().unwrap();
              
              // &CStr → &str
              let c_str: &CStr = c_string.as_c_str();
              let s: &str = c_str.to_str().unwrap();
              }

              よくある変換パターン

              関数の引数として

              #![allow(unused)]
              fn main() {
              // &strを受け取る(推奨)
              fn process(s: &str) {
                  // String も &str も渡せる
              }
              
              // 使用
              process("literal");           // &str
              process(&String::from("s"));  // &String → &str(自動deref)
              process(String::from("s").as_str()); // 明示的
              }

              構造体のフィールドとして

              #![allow(unused)]
              fn main() {
              // 所有権が必要な場合
              struct User {
                  name: String,  // 所有する
              }
              
              // 参照でよい場合(ライフタイム必要)
              struct UserRef<'a> {
                  name: &'a str,
              }
              }

              戻り値として

              #![allow(unused)]
              fn main() {
              // 新しい文字列を作る場合
              fn create_greeting(name: &str) -> String {
                  format!("Hello, {}!", name)
              }
              
              // 入力の一部を返す場合(ライフタイム必要)
              fn first_word(s: &str) -> &str {
                  &s[..s.find(' ').unwrap_or(s.len())]
              }
              }

              まとめ

              変換方法
              &strString.to_string(), .to_owned()
              String&str&s, .as_str()
              数値 → String.to_string(), format!()
              String → 数値.parse()
              Vec<u8>StringString::from_utf8()
              StringVec<u8>.into_bytes()

              基本方針:

              • 関数の引数は &str で受け取る(柔軟性)
              • 所有権が必要なら String を使う
              • 変換が必要な時は目的に合った方法を選ぶ

              エラーメッセージの読み方

              Rustコンパイラのエラーメッセージを正しく読み解く方法を学びます。

              エラーメッセージの構造

              error[E0382]: borrow of moved value: `s`
               --> src/main.rs:4:20
                |
              2 |     let s = String::from("hello");
                |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
              3 |     let s2 = s;
                |              - value moved here
              4 |     println!("{}", s);
                |                    ^ value borrowed here after move
                |
              help: consider cloning the value if the performance cost is acceptable
                |
              3 |     let s2 = s.clone();
                |               ++++++++
              

              各部分の説明

              1. エラーコード: error[E0382]

                • E0382 は一意のエラーコード
                • rustc --explain E0382 で詳細な説明が見られる
              2. エラーの要約: borrow of moved value: 's'

                • 何が問題かの簡潔な説明
              3. ファイル位置: --> src/main.rs:4:20

                • ファイル名:行番号:列番号
              4. コードスニペット: エラー箇所のコードと注釈

                • | の後にコードが表示される
                • ^- でエラー箇所を指示
                • 追加の説明が付く
              5. help: 修正方法の提案

                • 具体的なコード修正が提案される

              エラーレベル

              error(エラー)

              error[E0382]: ...
              
              • コンパイルが失敗する
              • 必ず修正が必要

              warning(警告)

              warning: unused variable: `x`
              
              • コンパイルは成功する
              • 潜在的な問題を示唆
              • 修正することを推奨

              note(注記)

              note: required by a bound in `HashMap::insert`
              
              • 追加の情報を提供
              • エラーの原因を理解するのに役立つ

              help(ヘルプ)

              help: consider borrowing here: `&x`
              
              • 具体的な修正案
              • 積極的に参考にしよう

              読み方のコツ

              1. エラーコードを確認する

              # 詳細な説明を見る
              rustc --explain E0382
              

              これにより、エラーの詳細な説明と例が表示されます。

              2. 最初のエラーから修正する

              error[E0382]: ...
              error[E0599]: ...
              error[E0308]: ...
              

              複数のエラーがある場合、最初のエラーを修正すると他のエラーも消えることが多い

              3. 注釈を丁寧に読む

              2 |     let s = String::from("hello");
                |         - move occurs because `s` has type `String`
                        ↑ この説明が重要
              

              -^ の横の説明文が問題の原因を教えてくれます。

              4. helpを活用する

              help: consider cloning the value
                |
              3 |     let s2 = s.clone();
                |               ++++++++
              

              + で囲まれた部分が追加すべきコードです。

              実践例

              例1: 型の不一致

              error[E0308]: mismatched types
               --> src/main.rs:2:18
                |
              2 |     let x: i32 = "hello";
                |            ---   ^^^^^^^ expected `i32`, found `&str`
                |            |
                |            expected due to this
              

              読み方:

              1. mismatched types: 型が一致しない
              2. expected i32, found &str: i32を期待したが&strがあった
              3. expected due to this: 型注釈i32が原因

              修正:

              #![allow(unused)]
              fn main() {
              let x: i32 = 42;        // i32にする
              // または
              let x: &str = "hello";  // &strにする
              }

              例2: 借用ルール違反

              error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
               --> src/main.rs:4:5
                |
              3 |     let first = &v[0];
                |                  - immutable borrow occurs here
              4 |     v.push(4);
                |     ^^^^^^^^^ mutable borrow occurs here
              5 |     println!("{}", first);
                |                    ----- immutable borrow later used here
              

              読み方:

              1. cannot borrow as mutable: 可変借用できない
              2. immutable borrow occurs here: 行3で不変借用
              3. mutable borrow occurs here: 行4で可変借用しようとした
              4. immutable borrow later used here: 行5で不変借用がまだ使われている

              修正: 不変借用を先に使い切る

              #![allow(unused)]
              fn main() {
              let mut v = vec![1, 2, 3];
              let first = &v[0];
              println!("{}", first);  // 先に使う
              v.push(4);              // その後で変更
              }

              例3: ライフタイム不足

              error[E0106]: missing lifetime specifier
               --> src/main.rs:1:33
                |
              1 | fn longest(x: &str, y: &str) -> &str {
                |               ----     ----     ^ expected named lifetime parameter
                |
                = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
              help: consider introducing a named lifetime parameter
                |
              1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
                |           ++++     ++          ++          ++
              

              読み方:

              1. missing lifetime specifier: ライフタイム指定子がない
              2. help: 戻り値が借用値だが、xyのどちらか不明
              3. consider introducing...: 具体的な修正案

              修正: helpの提案通りに

              #![allow(unused)]
              fn main() {
              fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
                  if x.len() > y.len() { x } else { y }
              }
              }

              例4: トレイト未実装

              error[E0277]: `MyStruct` doesn't implement `Debug`
                 --> src/main.rs:8:22
                  |
              8   |     println!("{:?}", my_struct);
                  |                      ^^^^^^^^^ `MyStruct` cannot be formatted using `{:?}`
                  |
                  = help: the trait `Debug` is not implemented for `MyStruct`
                  = note: add `#[derive(Debug)]` to `MyStruct` or manually `impl Debug for MyStruct`
              

              読み方:

              1. doesn't implement Debug: Debugトレイトが実装されていない
              2. cannot be formatted using {:?}: デバッグフォーマットが使えない
              3. note: deriveするか手動実装するか

              修正:

              #![allow(unused)]
              fn main() {
              #[derive(Debug)]  // これを追加
              struct MyStruct {
                  value: i32,
              }
              }

              エラーメッセージのパターン

              パターン1: 所有権・借用関連

              キーワード:

              • moved, borrow, mutable, immutable
              • does not live long enough, lifetime

              対処: Phase 2の内容を復習

              パターン2: 型関連

              キーワード:

              • mismatched types, expected, found
              • trait bound is not satisfied

              対処: 型を確認、deriveを追加

              パターン3: 未定義・スコープ関連

              キーワード:

              • cannot find, unresolved
              • not found in this scope

              対処: import文の確認、スペルチェック

              パターン4: 非同期関連

              キーワード:

              • async, await, Future
              • Send, Sync

              対処: async関数か確認、Send制約を満たす

              デバッグ用ツール

              型を調べる

              #![allow(unused)]
              fn main() {
              // コンパイルエラーで型を教えてもらう
              let x: () = some_expression;
              // error: expected `()`, found `ActualType`
              }

              dbg!マクロ

              #![allow(unused)]
              fn main() {
              let x = dbg!(some_expression);
              // [src/main.rs:1] some_expression = value
              }

              cargo check

              # コンパイルせずにエラーチェック(高速)
              cargo check
              

              clippy(リンター)

              # より詳細な警告と提案
              cargo clippy
              

              まとめ

              読むべき箇所目的
              エラーコード詳細説明を調べる(rustc --explain
              要約メッセージ何が問題かを把握
              コードスニペットどこが問題かを特定
              注釈(-や^)原因の詳細を理解
              help具体的な修正方法

              心がけ:

              1. 最初のエラーから順に修正
              2. helpの提案を積極的に採用
              3. エラーコードで詳細を調べる
              4. 落ち着いて読む - Rustコンパイラは親切!

              生成AI活用ガイドライン

              Rust学習において生成AIを効果的に活用する方法をまとめました。

              基本方針

              ┌─────────────────────────────────────────────────────┐
              │                  理解すべき領域                      │
              │  (AIに頼る前に自分で理解する)                       │
              │                                                     │
              │  ・所有権、借用、ライフタイム                         │
              │  ・エラーメッセージの読み方                           │
              │  ・基本的な型システム                                │
              │  ・プロジェクト構造                                  │
              │                                                     │
              ├─────────────────────────────────────────────────────┤
              │                  AI活用推奨領域                       │
              │  (積極的にAIを活用する)                            │
              │                                                     │
              │  ・ボイラープレートコード生成                         │
              │  ・ライブラリの使い方                                │
              │  ・テストコード生成                                  │
              │  ・デバッグの補助                                    │
              │                                                     │
              └─────────────────────────────────────────────────────┘
              

              人間が必ず理解すべきこと

              1. 所有権とメモリ管理

              なぜ人間が理解すべきか:

              • Rustの核心概念であり、すべてのコードに影響する
              • AIの生成コードでも所有権エラーは頻発する
              • 理解なしにAIの提案を採用すると、不適切な回避策を使いがち

              学習のポイント:

              #![allow(unused)]
              fn main() {
              // この3パターンを完全に理解する
              let s1 = String::from("hello");
              let s2 = s1;         // ムーブ
              let s3 = s2.clone(); // クローン
              let r = &s3;         // 借用
              }

              2. エラーメッセージの読解

              なぜ人間が理解すべきか:

              • エラーの原因を理解しないと、同じ間違いを繰り返す
              • AIにエラーを丸投げすると、表面的な修正になりがち
              • Rustコンパイラのエラーは非常に親切で情報量が多い

              実践:

              # 必ず自分でエラーを読む習慣をつける
              cargo build 2>&1 | less
              
              # エラーコードの詳細を調べる
              rustc --explain E0382
              

              3. Result/Optionの扱い

              なぜ人間が理解すべきか:

              • ほぼすべてのRustコードで使われる
              • エラー処理の設計に直接影響
              • unwrap()の乱用を防ぐ

              理解すべきパターン:

              #![allow(unused)]
              fn main() {
              // これらの違いを理解する
              result.unwrap()      // パニックの可能性
              result?              // エラー伝播
              result.unwrap_or(default)  // デフォルト値
              match result { ... }  // 明示的なハンドリング
              }

              4. プロジェクト構造

              なぜ人間が理解すべきか:

              • ファイルの配置がモジュールシステムに直結
              • AIは既存の構造を無視した提案をすることがある
              • 保守性に大きく影響

              理解すべき構造:

              src/
              ├── main.rs      # mod宣言
              ├── lib.rs       # ライブラリのルート
              ├── module.rs    # mod module;
              └── module/
                  └── mod.rs   # mod module;(ディレクトリ版)
              

              AI活用が効果的な領域

              1. ボイラープレートコード

              適している理由:

              • 定型的なコードは正確に生成できる
              • 時間の節約になる
              • ミスを減らせる

              プロンプト例:

              以下の構造体に対して、serde を使った
              JSON シリアライゼーションのボイラープレートを生成してください。
              
              struct User {
                  id: u32,
                  name: String,
                  email: String,
              }
              

              期待される出力:

              #![allow(unused)]
              fn main() {
              use serde::{Deserialize, Serialize};
              
              #[derive(Debug, Clone, Serialize, Deserialize)]
              struct User {
                  id: u32,
                  name: String,
                  email: String,
              }
              }

              2. ライブラリの使い方

              適している理由:

              • ドキュメントを読む時間を短縮
              • 具体的な使用例を素早く得られる
              • 最新のAPIを把握

              プロンプト例:

              reqwest を使って JSON を POST する方法を教えてください。
              エラーハンドリングも含めてください。
              

              3. テストコード生成

              適している理由:

              • テストケースの網羅性を高められる
              • 定型的なテストは正確に生成できる
              • テスト駆動開発を加速

              プロンプト例:

              以下の関数のユニットテストを生成してください。
              正常系、異常系、エッジケースを含めてください。
              
              fn divide(a: i32, b: i32) -> Option<i32> {
                  if b == 0 { None } else { Some(a / b) }
              }
              

              4. エラー解決の補助

              適している理由:

              • エラーメッセージの解釈を補助
              • 複数の解決策を提示してもらえる
              • 自分で考えた上での確認に使える

              プロンプト例:

              以下のエラーが出ています。
              原因と解決方法を教えてください。
              
              error[E0502]: cannot borrow `v` as mutable because
              it is also borrowed as immutable
              

              AIを使う際の注意点

              1. 生成コードを必ず理解する

              #![allow(unused)]
              fn main() {
              // AIが生成したコード
              async fn fetch_data() -> Result<String, Box<dyn Error>> {
                  // このコードの各部分を理解できるか?
                  let response = reqwest::get("https://...")
                      .await?           // <- なぜ?が必要?
                      .text()
                      .await?;          // <- なぜawaitが2回?
                  Ok(response)
              }
              }

              チェックリスト:

              • なぜこの型が使われているか理解した
              • エラー処理の方法を理解した
              • 所有権の流れを理解した

              2. セキュリティに注意

              避けるべきこと:

              #![allow(unused)]
              fn main() {
              // AIが生成しがちな危険なパターン
              
              // パスワードを環境変数からそのまま使用
              let password = std::env::var("PASSWORD").unwrap();
              
              // 入力値を検証せずにSQL文を構築
              let query = format!("SELECT * FROM users WHERE id = {}", user_input);
              
              // unwrap()の乱用
              let data = file.read_to_string().unwrap();
              }

              正しいアプローチ:

              #![allow(unused)]
              fn main() {
              // 環境変数は適切に処理
              let password = std::env::var("PASSWORD")
                  .expect("PASSWORD environment variable must be set");
              
              // パラメータバインディングを使用
              sqlx::query("SELECT * FROM users WHERE id = ?")
                  .bind(user_input)
              
              // 適切なエラーハンドリング
              let data = file.read_to_string()
                  .map_err(|e| AppError::FileRead(e))?;
              }

              3. 最新情報かどうか確認

              AIの知識には期限があります:

              #![allow(unused)]
              fn main() {
              // 古い書き方かもしれない
              extern crate serde;  // Rust 2018以降は不要
              
              // 非推奨のAPIかもしれない
              std::mem::uninitialized()  // 非推奨
              
              // バージョンが古いかもしれない
              axum = "0.5"  // 最新は0.7
              }

              確認方法:

              • crates.io で最新バージョンを確認
              • 公式ドキュメントと照合
              • cargo clippy で警告をチェック

              4. 文脈を伝える

              悪いプロンプト:

              Rustでログイン機能を作って
              

              良いプロンプト:

              Axum 0.7 と SQLx を使った Web API で、
              セッションベースのログイン機能を実装したいです。
              
              要件:
              - POST /login でメールアドレスとパスワードを受け取る
              - パスワードはargon2でハッシュ化されている
              - ログイン成功時はセッションにユーザーIDを保存
              - 適切なエラーレスポンスを返す
              
              現在のプロジェクト構造:
              src/
              ├── main.rs
              ├── handlers/
              └── models/
              
              使用しているクレート:
              - axum = "0.7"
              - sqlx = { version = "0.7", features = ["sqlite"] }
              - tower-sessions = "0.12"
              

              効果的なプロンプトのテンプレート

              コード生成

              [言語/フレームワーク]: Rust / Axum 0.7
              [目的]: [何を達成したいか]
              [制約]: [使用するクレート、バージョン、規約など]
              [現在のコード]: [関連するコードがあれば]
              
              期待する出力:
              - [出力形式の指定]
              - [エラーハンドリングの要件]
              - [その他の要件]
              

              デバッグ

              [エラーメッセージ]:
              

              [エラーの全文を貼り付け]

              
              [関連するコード]:
              ```rust
              [問題のコード]
              
              
              ### コードレビュー
              
              

              以下のコードをレビューしてください。

              観点:

              • 所有権とライフタイムの適切さ
              • エラーハンドリング
              • パフォーマンス
              • Rustらしい書き方か
              #![allow(unused)]
              fn main() {
              [レビュー対象のコード]
              }
              
              ## 学習段階別のAI活用
              
              ### Phase 0-1(入門期)
              
              **推奨度: 低め**
              
              - 基本文法は自分で書く
              - エラーは自分で解決を試みる
              - AIは「答え合わせ」に使う
              
              ### Phase 2(所有権学習期)
              
              **推奨度: 低め**
              
              - 所有権エラーは自分で理解する
              - AIの提案より自分の理解を優先
              - エラーメッセージの読み方を習得
              
              ### Phase 3-4(実践期)
              
              **推奨度: 中程度**
              
              - ボイラープレートはAIに任せる
              - ライブラリの使い方はAIに聞く
              - 生成コードは必ずレビュー
              
              ### Phase 5以降(応用期)
              
              **推奨度: 高め**
              
              - 設計相談にAIを活用
              - コードレビューを依頼
              - テスト生成を活用
              
              ## まとめ
              
              ### 人間が理解必須
              
              | 項目 | 理由 |
              |------|------|
              | 所有権・借用 | Rustの根幹、すべてに影響 |
              | エラーメッセージ | 自立したデバッグ能力 |
              | Result/Option | エラー処理の基本 |
              | プロジェクト構造 | 保守性・拡張性 |
              
              ### AI活用推奨
              
              | 項目 | 効果 |
              |------|------|
              | ボイラープレート | 時間節約 |
              | ライブラリ使用例 | 学習効率向上 |
              | テスト生成 | 品質向上 |
              | デバッグ補助 | 問題解決加速 |
              
              ### 鉄則
              
              1. **生成コードは必ず理解してから使う**
              2. **セキュリティに関わる部分は特に注意**
              3. **文脈を詳しく伝える**
              4. **最新情報かどうか確認する**
              5. **AIは道具、理解は人間の仕事**