Skilore

Rust概要 -- 安全性・パフォーマンス・所有権が融合したシステムプログラミング言語

Rustは「安全性」「速度」「並行性」の3つを同時に達成する唯一のシステムプログラミング言語であり、ガベージコレクタなしでメモリ安全を保証する。

82 分で読めます40,584 文字

Rust概要 -- 安全性・パフォーマンス・所有権が融合したシステムプログラミング言語

Rustは「安全性」「速度」「並行性」の3つを同時に達成する唯一のシステムプログラミング言語であり、ガベージコレクタなしでメモリ安全を保証する。


この章で学ぶこと

  1. Rustの設計哲学 -- ゼロコスト抽象化・所有権・型安全の3本柱を理解する
  2. 他言語との違い -- C/C++/Go/Pythonとの比較で立ち位置を把握する
  3. エコシステムの全体像 -- Cargo、crates.io、ツールチェーンの構成を掴む
  4. 言語の基本構文 -- 変数、関数、制御フロー、パターンマッチングの基礎を学ぶ
  5. 実践的な開発フロー -- プロジェクト作成からテスト・ドキュメント生成までの一連の流れを体験する

前提知識

このガイドを読む前に、以下の知識があると理解が深まります:

  • 基本的なプログラミングの知識
  • 関連する基礎概念の理解

1. Rustの歴史と設計哲学

1.1 誕生の経緯

+-----------------------------------------------------------+
|  2006  Graydon Hoare が個人プロジェクトとして開始          |
|  2009  Mozilla が公式にスポンサー                          |
|  2010  初の公開発表                                        |
|  2012  Rust 0.1 リリース(初の公式プレリリース)           |
|  2015  Rust 1.0 安定版リリース                             |
|  2018  Rust 2018 Edition (NLL, async 準備)                 |
|  2020  Mozilla リストラ後もコミュニティが継続               |
|  2021  Rust 2021 Edition / Rust Foundation 設立            |
|  2022  Linux カーネルが Rust を公式サポート                |
|  2024  Rust 2024 Edition                                   |
+-----------------------------------------------------------+

Rustは元々、Mozilla のエンジニアである Graydon Hoare が2006年に個人プロジェクトとして開発を始めた言語である。Hoare は C++ でブラウザエンジンの開発に携わる中で、メモリ安全性とパフォーマンスを両立する言語の必要性を感じ、Rust の設計を開始した。

2009年に Mozilla が公式スポンサーとなり、Servo ブラウザエンジンの開発言語として採用された。Servo プロジェクトは Rust の実用性を証明する場となり、並行処理やメモリ安全性に関する多くの知見がフィードバックされた。

2015年5月15日に Rust 1.0 が安定版としてリリースされ、後方互換性の保証が始まった。以降、6週間ごとのリリースサイクルで継続的に改善が続けられている。

2021年には Rust Foundation が設立され、AWS、Google、Huawei、Microsoft、Mozilla の5社が創設メンバーとなった。これにより、言語の長期的な持続可能性が確保された。

1.2 設計原則

+---------------------+---------------------+---------------------+
|  安全性 (Safety)    |  速度 (Speed)       |  並行性 (Concur.)   |
+---------------------+---------------------+---------------------+
| - 所有権システム    | - ゼロコスト抽象化  | - Send / Sync       |
| - 借用チェッカー    | - LLVMバックエンド  | - データ競合防止     |
| - ライフタイム      | - インライン展開    | - fearless concur.  |
| - Optionでnull排除  | - 単態化           | - async/await       |
| - Result型でエラー  | - スタック優先      | - チャネル通信       |
| - unsafe境界の明示  | - SIMD対応         | - Arc/Mutex          |
+---------------------+---------------------+---------------------+

ゼロコスト抽象化 (Zero-Cost Abstractions)

Rustの最も重要な設計原則の一つがゼロコスト抽象化である。Bjarne Stroustrup が C++ で提唱した原則を Rust は徹底している:

「使わないものにはコストを払わない。使うものについては、手書きでこれ以上効率的に書くことはできない」

Rustではイテレータ、ジェネリクス、トレイトなどの高レベルな抽象化を使っても、コンパイラが最適化した結果は手書きの低レベルコードと同等の性能を発揮する。

// 高レベルなイテレータチェーン
let sum: i32 = (0..1000)
    .filter(|x| x % 2 == 0)
    .map(|x| x * x)
    .sum();
 
// コンパイラはこれを手書きのループと同等の機械語に最適化する
// LLVM の最適化パスにより、中間的なイテレータオブジェクトは完全に消去される

型安全性 (Type Safety)

Rustの型システムは、C/C++ では実行時にしか発見できない多くのバグをコンパイル時に検出する。null ポインタの代わりに Option<T>、例外の代わりに Result<T, E> を使うことで、エラー処理のパスが型レベルで強制される。

// null の代わりに Option を使う
fn find_user(id: u64) -> Option<User> {
    // None を返すことで「見つからなかった」を安全に表現
    if id == 0 { return None; }
    Some(User { id, name: "example".to_string() })
}
 
// 呼び出し側は None のケースを必ず処理する必要がある
match find_user(42) {
    Some(user) => println!("見つかった: {}", user.name),
    None => println!("ユーザーが見つかりません"),
}

unsafe の境界

Rustは完全な安全性を保証しつつも、unsafe キーワードによって低レベルな操作を行う「脱出ハッチ」を提供する。これにより、OSのシステムコール、FFI(外部関数インターフェース)、パフォーマンスに特化した最適化が可能になる。

// unsafe ブロックでは以下の操作が許可される:
// 1. 生ポインタのデリファレンス
// 2. unsafe な関数・メソッドの呼び出し
// 3. 可変なスタティック変数へのアクセス
// 4. unsafe トレイトの実装
// 5. union のフィールドアクセス
 
fn raw_pointer_example() {
    let mut num = 5;
    let r1 = &num as *const i32;     // 生ポインタ(不変)
    let r2 = &mut num as *mut i32;   // 生ポインタ(可変)
 
    unsafe {
        println!("r1 = {}", *r1);
        *r2 = 10;
        println!("r2 = {}", *r2);
    }
}

重要なのは、unsafe はコンパイラの安全性チェックを「一部」無効にするだけであり、所有権やライフタイムのルール自体は依然として適用されるという点である。

1.3 Rustが解決する問題

Rustは主に以下の問題を解決するために設計された:

C/C++ の問題Rust の解決策
ダングリングポインタ所有権 + ライフタイムで防止
ダブルフリームーブセマンティクスで防止
バッファオーバーフロー境界チェック + スライスで防止
データ競合借用規則 + Send/Sync で防止
null ポインタ参照Option<T> で型レベルで防止
メモリリーク(一般的)RAII + Drop で自動管理
未初期化変数の使用コンパイラが初期化を強制
整数オーバーフローデバッグビルドでパニック

2. コード例

例1: Hello, World!

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

println! はマクロであり(末尾の ! が目印)、コンパイル時にフォーマット文字列を検証する。C の printf のようなフォーマット文字列の不整合はコンパイルエラーになる。

例2: 変数とイミュータビリティ

fn main() {
    let x = 5;          // デフォルトで不変
    // x = 6;           // コンパイルエラー!
    let mut y = 10;     // mut で可変宣言
    y += 1;
    println!("x={}, y={}", x, y);
 
    // シャドウイング: 同じ名前で新しい変数を束縛
    let x = x + 1;      // 新しい x(型を変えることも可能)
    let x = x * 2;
    println!("シャドウイング後の x = {}", x); // 12
 
    // シャドウイングでは型を変更できる
    let spaces = "   ";           // &str 型
    let spaces = spaces.len();    // usize 型に変更
    println!("スペース数: {}", spaces);
}

Rustではデフォルトで変数は不変(immutable)である。これは関数型プログラミングの影響を受けた設計であり、不変性をデフォルトにすることでコードの安全性と予測可能性が向上する。可変にする必要がある場合は mut キーワードを明示的に付ける。

例3: 所有権の基本

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;        // s1 の所有権が s2 にムーブ
    // println!("{}", s1); // コンパイルエラー: s1 は無効
    println!("{}", s2);
}

例4: 関数と戻り値

fn add(a: i32, b: i32) -> i32 {
    a + b   // セミコロンなし = 式として返却
}
 
fn main() {
    let result = add(3, 4);
    println!("3 + 4 = {}", result);
}

Rustでは関数の最後の式がセミコロンなしで書かれると、それが戻り値として扱われる。return キーワードは早期リターンの場合にのみ使用するのが慣例である。

例5: パターンマッチング

enum Direction {
    North,
    South,
    East,
    West,
}
 
fn describe(dir: Direction) -> &'static str {
    match dir {
        Direction::North => "北",
        Direction::South => "南",
        Direction::East  => "東",
        Direction::West  => "西",
    }
}
 
fn main() {
    println!("{}", describe(Direction::North));
}

match 式は網羅的(exhaustive)であることが要求される。全てのパターンをカバーしていないとコンパイルエラーになるため、パターンの漏れを防止できる。

例6: 構造体とメソッド

struct Point {
    x: f64,
    y: f64,
}
 
impl Point {
    // 関連関数(コンストラクタの慣例)
    fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }
 
    // メソッド(&self を受け取る)
    fn distance(&self, other: &Point) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }
 
    // 原点からの距離
    fn magnitude(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
 
    // 可変メソッド(&mut self を受け取る)
    fn translate(&mut self, dx: f64, dy: f64) {
        self.x += dx;
        self.y += dy;
    }
}
 
fn main() {
    let a = Point::new(0.0, 0.0);
    let b = Point::new(3.0, 4.0);
    println!("距離: {}", a.distance(&b)); // 5.0
    println!("bの大きさ: {}", b.magnitude()); // 5.0
 
    let mut c = Point::new(1.0, 1.0);
    c.translate(2.0, 3.0);
    println!("移動後: ({}, {})", c.x, c.y); // (3.0, 4.0)
}

例7: 制御フロー

fn main() {
    // if 式(Rustでは if は式として値を返す)
    let number = 7;
    let kind = if number % 2 == 0 { "偶数" } else { "奇数" };
    println!("{} は {}", number, kind);
 
    // loop(無限ループ、break で値を返せる)
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2; // 20 が result に束縛される
        }
    };
    println!("loop の結果: {}", result);
 
    // while ループ
    let mut n = 5;
    while n > 0 {
        println!("{}!", n);
        n -= 1;
    }
    println!("発射!");
 
    // for ループ(Range)
    for i in 1..=5 {
        print!("{} ", i); // 1 2 3 4 5
    }
    println!();
 
    // for ループ(コレクション)
    let fruits = vec!["りんご", "みかん", "バナナ"];
    for (i, fruit) in fruits.iter().enumerate() {
        println!("{}: {}", i, fruit);
    }
 
    // while let(パターンマッチングと組み合わせ)
    let mut stack = vec![1, 2, 3];
    while let Some(top) = stack.pop() {
        println!("ポップ: {}", top);
    }
}

例8: クロージャの基本

fn main() {
    // クロージャ(無名関数)
    let add = |a: i32, b: i32| -> i32 { a + b };
    println!("3 + 4 = {}", add(3, 4));
 
    // 型推論でより簡潔に
    let multiply = |a, b| a * b;
    println!("3 * 4 = {}", multiply(3, 4));
 
    // 環境のキャプチャ
    let offset = 10;
    let add_offset = |x| x + offset;
    println!("5 + 10 = {}", add_offset(5));
 
    // イテレータとの組み合わせ
    let numbers = vec![1, 2, 3, 4, 5];
    let sum: i32 = numbers.iter()
        .filter(|&&x| x > 2)
        .map(|&x| x * x)
        .sum();
    println!("2より大きい数の二乗和: {}", sum); // 9 + 16 + 25 = 50
}

例9: トレイトの基本

trait Printable {
    fn format_string(&self) -> String;
 
    // デフォルト実装
    fn print(&self) {
        println!("{}", self.format_string());
    }
}
 
struct Article {
    title: String,
    content: String,
}
 
impl Printable for Article {
    fn format_string(&self) -> String {
        format!("【記事】{}\n{}", self.title, self.content)
    }
}
 
struct Tweet {
    user: String,
    message: String,
}
 
impl Printable for Tweet {
    fn format_string(&self) -> String {
        format!("@{}: {}", self.user, self.message)
    }
}
 
// トレイト境界を使った関数
fn display_item(item: &impl Printable) {
    item.print();
}
 
fn main() {
    let article = Article {
        title: "Rust入門".to_string(),
        content: "Rustは素晴らしい言語です".to_string(),
    };
    let tweet = Tweet {
        user: "rustlang".to_string(),
        message: "Rust 1.0 released!".to_string(),
    };
 
    display_item(&article);
    display_item(&tweet);
}

例10: エラーハンドリングの基本

use std::fs;
use std::io;
 
fn read_username_from_file() -> Result<String, io::Error> {
    let content = fs::read_to_string("username.txt")?;
    Ok(content.trim().to_string())
}
 
fn main() {
    match read_username_from_file() {
        Ok(name) => println!("ユーザー名: {}", name),
        Err(e) => eprintln!("エラー: {}", e),
    }
 
    // Option の活用例
    let numbers = vec![10, 20, 30];
    let first = numbers.first();
    match first {
        Some(n) => println!("最初の要素: {}", n),
        None => println!("空のベクタ"),
    }
 
    // if let による簡潔なパターンマッチ
    if let Some(n) = numbers.get(1) {
        println!("2番目の要素: {}", n);
    }
}

3. 図解

3.1 ビルドパイプライン

.rs───>rustc───>LLVM───>バイナリ
ソースフロントIR/最適化実行可能
エンドファイル
│                │
                     ▼                ▼
借用チェック最適化パス
型チェックインライン化
ライフタイムデッドコード
パターン検査除去、SIMD

ビルドパイプラインの詳細:

  1. 字句解析・構文解析: .rs ファイルを AST(抽象構文木)に変換
  2. 名前解決: 変数名・型名・モジュール名を解決
  3. 型チェック: 型の整合性を検証(型推論含む)
  4. 借用チェック: 所有権・借用・ライフタイムの規則を検証
  5. MIR生成: 中間表現(Mid-level IR)を生成
  6. MIR最適化: 定数伝播、デッドコード除去など
  7. LLVM IR生成: LLVM が理解する中間表現に変換
  8. LLVM最適化: インライン展開、ループ最適化、SIMD 変換など
  9. コード生成: ターゲットアーキテクチャ向けの機械語を生成
  10. リンク: オブジェクトファイルをリンクして実行可能バイナリを生成

3.2 メモリモデル(スタック vs ヒープ)

スタック                      ヒープ
ptr ──────────────────────>h e l l o
capacity = 5
ptr ──────────────────────>[1, 2, 3, 4, 5]
capacity = 8
x: i32 = 42
y: bool = true
z: f64 = 3.14
固定サイズ・高速            可変サイズ・動的
    LIFO(後入れ先出し)       OS が管理(malloc/free)
    自動的に確保・解放          Rust が RAII で自動管理

スタックとヒープの使い分け:

  • スタック: コンパイル時にサイズがわかる型(i32, f64, bool, 固定長配列, タプル)
  • ヒープ: 実行時にサイズが変わる型(String, Vec<T>, HashMap<K,V>, Box<T>

Rustの String 型はスタック上に3つのフィールド(ポインタ、長さ、容量)を持ち、実際の文字列データはヒープ上に格納される。所有者がスコープを抜けると、Drop トレイトによってヒープメモリが自動的に解放される。

3.3 ツールチェーン構成

rustup
(ツールチェーン管理: stable/beta/nightly)
rustccargoその他ツール
コンパイラビルドrustfmt -- コードフォーマッタ
パッケージclippy -- 静的解析リンター
テストrust-analyzer -- LSP サーバー
ドキュメントmiri -- 未定義動作検出
ベンチマークcargo-audit -- 脆弱性検査
cargo-expand -- マクロ展開確認
│
                ▼
crates.io

3.4 Rustのモジュールシステム

my_project/
├── Cargo.toml          # プロジェクト設定・依存関係
├── Cargo.lock          # 依存関係のロックファイル
├── src/
│   ├── main.rs         # バイナリクレートのエントリポイント
│   ├── lib.rs          # ライブラリクレートのルート
│   ├── config.rs       # モジュールファイル
│   └── utils/          # サブモジュールディレクトリ
│       ├── mod.rs      # utils モジュールのルート
│       ├── parser.rs   # utils::parser サブモジュール
│       └── formatter.rs# utils::formatter サブモジュール
├── tests/
│   └── integration_test.rs  # 統合テスト
├── benches/
│   └── benchmark.rs    # ベンチマーク
└── examples/
    └── demo.rs         # サンプルコード
// src/lib.rs
pub mod config;       // config.rs を読み込む
pub mod utils;        // utils/mod.rs を読み込む
 
// src/utils/mod.rs
pub mod parser;       // utils/parser.rs を読み込む
pub mod formatter;    // utils/formatter.rs を読み込む
 
// src/main.rs
use my_project::config::Config;
use my_project::utils::parser::parse;
 
fn main() {
    let config = Config::load("settings.toml").unwrap();
    let data = parse(&config.input_file).unwrap();
    println!("パース完了: {} 件", data.len());
}

3.5 Rustの所有権モデル概要図

所有権システム
┌────────┐ ムーブ ┌────────┐
変数 A──────>変数 BA は無効化される
└────────┘ └────────┘
┌────────┐ 借用 ┌────────┐
変数 A<──────参照&AA は有効なまま
└────────┘ └────────┘
┌────────┐ クローン ┌────────┐
変数 A────────変数 B独立したコピー
└────────┘ └────────┘
ルール:
1. 各値は唯一の所有者を持つ
2. 所有者がスコープを抜けると Drop が呼ばれる
3. 不変参照は同時に複数可能
4. 可変参照は同時に1つだけ
5. 参照は常に有効でなければならない

4. 比較表

4.1 Rust vs 他のシステム言語

特性 Rust C C++ Go Python
メモリ安全 コンパイル時保証 手動管理 手動+RAII GC GC
データ競合防止 コンパイル時保証 なし なし 実行時検出 GIL
ゼロコスト抽象化 あり N/A あり なし(GC) なし
パッケージマネージャ Cargo (標準) なし (CMakeなど) なし (vcpkgなど) go mod pip
学習曲線 急峻 中程度 急峻 緩やか 緩やか
コンパイル速度 遅め 速い 遅い 非常に速い N/A
null Option型 NULLポインタ nullptr nil None
エラー処理 Result型 戻り値/errno 例外 error戻り値 例外
並行処理 async/await, スレッド pthread std::thread goroutine asyncio
バイナリサイズ 小さい 最小 大きめ やや大きい N/A
クロスコンパイル 容易 複雑 複雑 非常に容易 N/A
Wasm対応 優秀 Emscripten Emscripten TinyGo Pyodide

4.2 Rust Edition 比較

機能 2015 2018 2021 2024
NLL (Non-Lexical Lifetimes) - 導入 安定 安定
async/await - 導入 安定 改善
モジュールパス 旧方式 新方式 新方式 新方式
クロージャキャプチャ 全体 全体 部分 部分
dyn Trait 暗黙 明示必須 明示必須 明示必須
try ブロック - - - 安定化へ
let-else - - 導入 安定
impl Trait in type aliases - - - 安定化へ

4.3 Rustの適用領域

領域 代表的なプロジェクト Rustの強み
Webバックエンド Actix Web, Axum, Rocket 高スループット、低メモリ
CLI ツール ripgrep, bat, fd, exa 高速起動、シングルバイナリ
WebAssembly wasm-bindgen, Yew, Leptos 小さなバイナリ、高速実行
組み込み embedded-hal, RTIC ゼロコスト、no_std対応
OS/カーネル Redox OS, Linux ドライバ メモリ安全、低レベル制御
ゲーム Bevy, Amethyst 高パフォーマンス、ECS
ブロックチェーン Solana, Polkadot, Near 安全性、パフォーマンス
データベース TiKV, SurrealDB 並行処理、信頼性
ネットワーク Tokio, Hyper, Cloudflare 非同期I/O、低レイテンシ

5. Cargoの使い方

5.1 基本コマンド

# プロジェクト作成
cargo new my_project          # バイナリプロジェクト
cargo new my_lib --lib        # ライブラリプロジェクト
cargo init                    # 既存ディレクトリで初期化
 
# ビルドと実行
cargo build                   # デバッグビルド
cargo build --release         # リリースビルド(最適化あり)
cargo run                     # ビルドして実行
cargo run --release           # リリースモードで実行
cargo run -- arg1 arg2        # 引数付き実行
 
# テスト
cargo test                    # 全テスト実行
cargo test test_name          # 特定テスト実行
cargo test -- --nocapture     # println! を表示
cargo test --doc              # ドキュメントテストのみ
 
# 品質ツール
cargo clippy                  # 静的解析
cargo fmt                     # コードフォーマット
cargo doc --open              # ドキュメント生成・表示
cargo audit                   # セキュリティ監査(要インストール)
 
# 依存関係
cargo add serde               # 依存関係追加(cargo-edit)
cargo update                  # 依存関係更新
cargo tree                    # 依存関係ツリー表示
 
# その他
cargo bench                   # ベンチマーク実行
cargo clean                   # ビルド成果物を削除
cargo check                   # コンパイルチェック(バイナリ生成なし・高速)

5.2 Cargo.toml の構成

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "A useful project"
license = "MIT"
repository = "https://github.com/user/my_project"
 
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
clap = { version = "4", features = ["derive"] }
 
[dev-dependencies]
criterion = "0.5"
mockall = "0.12"
 
[profile.release]
opt-level = 3      # 最大最適化
lto = true          # リンク時最適化
codegen-units = 1   # コード生成ユニット数(遅いがより最適化)
strip = true        # デバッグシンボル除去
 
[profile.dev]
opt-level = 0       # 最適化なし(ビルド高速)
debug = true        # デバッグ情報あり

6. アンチパターン

アンチパターン1: 何でも clone() で解決する

// BAD: 所有権エラーを全て clone で回避
fn process(data: Vec<String>) {
    let copy = data.clone(); // 不必要なヒープアロケーション
    for item in data.clone() {
        println!("{}", item);
    }
}
 
// GOOD: 参照を活用する
fn process_good(data: &[String]) {
    for item in data {
        println!("{}", item);
    }
}

アンチパターン2: unwrap() の乱用

// BAD: 本番コードで unwrap は危険
fn read_config() -> String {
    std::fs::read_to_string("config.toml").unwrap() // パニック!
}
 
// GOOD: Result を適切に伝播
fn read_config_good() -> Result<String, std::io::Error> {
    std::fs::read_to_string("config.toml")
}

アンチパターン3: 不必要な String の所有

// BAD: 引数に String を要求する
fn greet(name: String) {
    println!("こんにちは、{}さん", name);
}
 
// GOOD: &str を受け取れば String も &str も渡せる
fn greet_good(name: &str) {
    println!("こんにちは、{}さん", name);
}
 
fn main() {
    let owned = String::from("太郎");
    let borrowed = "花子";
 
    greet_good(&owned);    // String → &str (自動 Deref)
    greet_good(borrowed);  // &str そのまま
}

アンチパターン4: 過度に深いネスト

// BAD: match のネストが深い
fn process_data(input: Option<Result<String, io::Error>>) {
    match input {
        Some(result) => {
            match result {
                Ok(data) => {
                    match data.parse::<i32>() {
                        Ok(num) => println!("{}", num),
                        Err(e) => eprintln!("パースエラー: {}", e),
                    }
                }
                Err(e) => eprintln!("IOエラー: {}", e),
            }
        }
        None => eprintln!("入力なし"),
    }
}
 
// GOOD: 早期リターンやコンビネータで平坦化
fn process_data_good(input: Option<Result<String, io::Error>>) -> Result<(), Box<dyn std::error::Error>> {
    let data = input.ok_or("入力なし")?;
    let text = data?;
    let num: i32 = text.parse()?;
    println!("{}", num);
    Ok(())
}

アンチパターン5: C スタイルの for ループ

// BAD: インデックスベースのループ
fn sum_vec(v: &[i32]) -> i32 {
    let mut sum = 0;
    for i in 0..v.len() {
        sum += v[i]; // 毎回境界チェックが走る
    }
    sum
}
 
// GOOD: イテレータを使う
fn sum_vec_good(v: &[i32]) -> i32 {
    v.iter().sum()
}

7. Rustのテスト

7.1 ユニットテスト

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
 
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("ゼロ除算エラー".to_string())
    } else {
        Ok(a / b)
    }
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
        assert_eq!(add(-1, 1), 0);
        assert_eq!(add(0, 0), 0);
    }
 
    #[test]
    fn test_divide_success() {
        let result = divide(10.0, 2.0);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 5.0);
    }
 
    #[test]
    fn test_divide_by_zero() {
        let result = divide(10.0, 0.0);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err(), "ゼロ除算エラー");
    }
 
    #[test]
    #[should_panic(expected = "index out of bounds")]
    fn test_out_of_bounds() {
        let v = vec![1, 2, 3];
        let _ = v[10]; // パニック!
    }
 
    #[test]
    fn test_with_result() -> Result<(), String> {
        let result = divide(10.0, 2.0)?;
        assert_eq!(result, 5.0);
        Ok(())
    }
}

7.2 ドキュメントテスト

/// 2つの数値を加算する。
///
/// # 引数
///
/// * `a` - 最初の数値
/// * `b` - 2番目の数値
///
/// # 戻り値
///
/// 2つの数値の合計
///
/// # 例
///
/// ```
/// use my_crate::add;
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

cargo test --doc でドキュメント内のコード例がテストとして実行される。これにより、ドキュメントのコード例が常に正しいことが保証される。


実践演習

演習1: 基本的な実装

以下の要件を満たすコードを実装してください。

要件:

  • 入力データの検証を行うこと
  • エラーハンドリングを適切に実装すること
  • テストコードも作成すること
# 演習1: 基本実装のテンプレート
class Exercise1:
    """基本的な実装パターンの演習"""
 
    def __init__(self):
        self.data = []
 
    def validate_input(self, value):
        """入力値の検証"""
        if value is None:
            raise ValueError("入力値がNoneです")
        return True
 
    def process(self, value):
        """データ処理のメインロジック"""
        self.validate_input(value)
        self.data.append(value)
        return self.data
 
    def get_results(self):
        """処理結果の取得"""
        return {
            'count': len(self.data),
            'data': self.data
        }
 
# テスト
def test_exercise1():
    ex = Exercise1()
    assert ex.process(1) == [1]
    assert ex.process(2) == [1, 2]
    assert ex.get_results()['count'] == 2
 
    try:
        ex.process(None)
        assert False, "例外が発生するべき"
    except ValueError:
        pass
 
    print("全テスト合格!")
 
test_exercise1()

演習2: 応用パターン

基本実装を拡張して、以下の機能を追加してください。

# 演習2: 応用パターン
from typing import List, Dict, Optional
from datetime import datetime
 
class AdvancedExercise:
    """応用パターンの演習"""
 
    def __init__(self, max_size: int = 100):
        self._items: List[Dict] = []
        self._max_size = max_size
        self._created_at = datetime.now()
 
    def add(self, key: str, value: any) -> bool:
        """アイテムの追加(サイズ制限付き)"""
        if len(self._items) >= self._max_size:
            return False
        self._items.append({
            'key': key,
            'value': value,
            'timestamp': datetime.now().isoformat()
        })
        return True
 
    def find(self, key: str) -> Optional[Dict]:
        """キーによる検索"""
        for item in reversed(self._items):
            if item['key'] == key:
                return item
        return None
 
    def remove(self, key: str) -> bool:
        """キーによる削除"""
        for i, item in enumerate(self._items):
            if item['key'] == key:
                self._items.pop(i)
                return True
        return False
 
    def stats(self) -> Dict:
        """統計情報"""
        return {
            'total_items': len(self._items),
            'max_size': self._max_size,
            'usage_percent': len(self._items) / self._max_size * 100,
            'uptime': str(datetime.now() - self._created_at)
        }
 
# テスト
def test_advanced():
    ex = AdvancedExercise(max_size=3)
    assert ex.add("a", 1) == True
    assert ex.add("b", 2) == True
    assert ex.add("c", 3) == True
    assert ex.add("d", 4) == False  # サイズ制限
    assert ex.find("b")['value'] == 2
    assert ex.remove("b") == True
    assert ex.find("b") is None
    stats = ex.stats()
    assert stats['total_items'] == 2
    print("応用テスト全合格!")
 
test_advanced()

演習3: パフォーマンス最適化

以下のコードのパフォーマンスを改善してください。

# 演習3: パフォーマンス最適化
import time
from functools import lru_cache
 
# 最適化前(O(n^2))
def slow_search(data: list, target: int) -> int:
    """非効率な検索"""
    for i in range(len(data)):
        for j in range(i + 1, len(data)):
            if data[i] + data[j] == target:
                return (i, j)
    return (-1, -1)
 
# 最適化後(O(n))
def fast_search(data: list, target: int) -> tuple:
    """ハッシュマップを使った効率的な検索"""
    seen = {}
    for i, num in enumerate(data):
        complement = target - num
        if complement in seen:
            return (seen[complement], i)
        seen[num] = i
    return (-1, -1)
 
# ベンチマーク
def benchmark():
    import random
    data = list(range(5000))
    random.shuffle(data)
    target = data[100] + data[4000]
 
    start = time.time()
    result1 = slow_search(data, target)
    slow_time = time.time() - start
 
    start = time.time()
    result2 = fast_search(data, target)
    fast_time = time.time() - start
 
    print(f"非効率版: {slow_time:.4f}秒")
    print(f"効率版:   {fast_time:.6f}秒")
    print(f"高速化率: {slow_time/fast_time:.0f}倍")
 
benchmark()

ポイント:

  • アルゴリズムの計算量を意識する
  • 適切なデータ構造を選択する
  • ベンチマークで効果を測定する

トラブルシューティング

よくあるエラーと解決策

エラー 原因 解決策
初期化エラー 設定ファイルの不備 設定ファイルのパスと形式を確認
タイムアウト ネットワーク遅延/リソース不足 タイムアウト値の調整、リトライ処理の追加
メモリ不足 データ量の増大 バッチ処理の導入、ページネーションの実装
権限エラー アクセス権限の不足 実行ユーザーの権限確認、設定の見直し
データ不整合 並行処理の競合 ロック機構の導入、トランザクション管理

デバッグの手順

  1. エラーメッセージの確認: スタックトレースを読み、発生箇所を特定する
  2. 再現手順の確立: 最小限のコードでエラーを再現する
  3. 仮説の立案: 考えられる原因をリストアップする
  4. 段階的な検証: ログ出力やデバッガを使って仮説を検証する
  5. 修正と回帰テスト: 修正後、関連する箇所のテストも実行する
# デバッグ用ユーティリティ
import logging
import traceback
from functools import wraps
 
# ロガーの設定
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
logger = logging.getLogger(__name__)
 
def debug_decorator(func):
    """関数の入出力をログ出力するデコレータ"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        logger.debug(f"呼び出し: {func.__name__}(args={args}, kwargs={kwargs})")
        try:
            result = func(*args, **kwargs)
            logger.debug(f"戻り値: {func.__name__} -> {result}")
            return result
        except Exception as e:
            logger.error(f"例外発生: {func.__name__}: {e}")
            logger.error(traceback.format_exc())
            raise
    return wrapper
 
@debug_decorator
def process_data(items):
    """データ処理(デバッグ対象)"""
    if not items:
        raise ValueError("空のデータ")
    return [item * 2 for item in items]

パフォーマンス問題の診断

パフォーマンス問題が発生した場合の診断手順:

  1. ボトルネックの特定: プロファイリングツールで計測
  2. メモリ使用量の確認: メモリリークの有無をチェック
  3. I/O待ちの確認: ディスクやネットワークI/Oの状況を確認
  4. 同時接続数の確認: コネクションプールの状態を確認
問題の種類 診断ツール 対策
CPU負荷 cProfile, py-spy アルゴリズム改善、並列化
メモリリーク tracemalloc, objgraph 参照の適切な解放
I/Oボトルネック strace, iostat 非同期I/O、キャッシュ
DB遅延 EXPLAIN, slow query log インデックス、クエリ最適化

設計判断ガイド

選択基準マトリクス

技術選択を行う際の判断基準を以下にまとめます。

判断基準 重視する場合 妥協できる場合
パフォーマンス リアルタイム処理、大規模データ 管理画面、バッチ処理
保守性 長期運用、チーム開発 プロトタイプ、短期プロジェクト
スケーラビリティ 成長が見込まれるサービス 社内ツール、固定ユーザー
セキュリティ 個人情報、金融データ 公開データ、社内利用
開発速度 MVP、市場投入スピード 品質重視、ミッションクリティカル

アーキテクチャパターンの選択

アーキテクチャ選択フロー
① チーム規模は?
├─ 小規模(1-5人)→ モノリス
└─ 大規模(10人+)→ ②へ
② デプロイ頻度は?
├─ 週1回以下 → モノリス + モジュール分割
└─ 毎日/複数回 → ③へ
③ チーム間の独立性は?
├─ 高い → マイクロサービス
└─ 中程度 → モジュラーモノリス

トレードオフの分析

技術的な判断には必ずトレードオフが伴います。以下の観点で分析を行いましょう:

1. 短期 vs 長期のコスト

  • 短期的に速い方法が長期的には技術的負債になることがある
  • 逆に、過剰な設計は短期的なコストが高く、プロジェクトの遅延を招く

2. 一貫性 vs 柔軟性

  • 統一された技術スタックは学習コストが低い
  • 多様な技術の採用は適材適所が可能だが、運用コストが増加

3. 抽象化のレベル

  • 高い抽象化は再利用性が高いが、デバッグが困難になる場合がある
  • 低い抽象化は直感的だが、コードの重複が発生しやすい
# 設計判断の記録テンプレート
class ArchitectureDecisionRecord:
    """ADR (Architecture Decision Record) の作成"""
 
    def __init__(self, title: str):
        self.title = title
        self.context = ""
        self.decision = ""
        self.consequences = []
        self.alternatives = []
 
    def set_context(self, context: str):
        """背景と課題の記述"""
        self.context = context
        return self
 
    def set_decision(self, decision: str):
        """決定内容の記述"""
        self.decision = decision
        return self
 
    def add_consequence(self, consequence: str, positive: bool = True):
        """結果の追加"""
        self.consequences.append({
            'description': consequence,
            'type': 'positive' if positive else 'negative'
        })
        return self
 
    def add_alternative(self, name: str, reason_rejected: str):
        """却下した代替案の追加"""
        self.alternatives.append({
            'name': name,
            'reason_rejected': reason_rejected
        })
        return self
 
    def to_markdown(self) -> str:
        """Markdown形式で出力"""
        md = f"# ADR: {self.title}\n\n"
        md += f"## 背景\n{self.context}\n\n"
        md += f"## 決定\n{self.decision}\n\n"
        md += "## 結果\n"
        for c in self.consequences:
            icon = "✅" if c['type'] == 'positive' else "⚠️"
            md += f"- {icon} {c['description']}\n"
        md += "\n## 却下した代替案\n"
        for a in self.alternatives:
            md += f"- **{a['name']}**: {a['reason_rejected']}\n"
        return md

実務での適用シナリオ

シナリオ1: スタートアップでのMVP開発

状況: 限られたリソースで素早くプロダクトをリリースする必要がある

アプローチ:

  • シンプルなアーキテクチャを選択
  • 必要最小限の機能に集中
  • 自動テストはクリティカルパスのみ
  • モニタリングは早期から導入

学んだ教訓:

  • 完璧を求めすぎない(YAGNI原則)
  • ユーザーフィードバックを早期に取得
  • 技術的負債は意識的に管理する

シナリオ2: レガシーシステムのモダナイゼーション

状況: 10年以上運用されているシステムを段階的に刷新する

アプローチ:

  • Strangler Fig パターンで段階的に移行
  • 既存のテストがない場合はCharacterization Testを先に作成
  • APIゲートウェイで新旧システムを共存
  • データ移行は段階的に実施
フェーズ 作業内容 期間目安 リスク
1. 調査 現状分析、依存関係の把握 2-4週間
2. 基盤 CI/CD構築、テスト環境 4-6週間
3. 移行開始 周辺機能から順次移行 3-6ヶ月
4. コア移行 中核機能の移行 6-12ヶ月
5. 完了 旧システム廃止 2-4週間

シナリオ3: 大規模チームでの開発

状況: 50人以上のエンジニアが同一プロダクトを開発する

アプローチ:

  • ドメイン駆動設計で境界を明確化
  • チームごとにオーナーシップを設定
  • 共通ライブラリはInner Source方式で管理
  • APIファーストで設計し、チーム間の依存を最小化
# チーム間のAPI契約定義
from dataclasses import dataclass
from typing import List, Optional
from enum import Enum
 
class Priority(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"
 
@dataclass
class APIContract:
    """チーム間のAPI契約"""
    endpoint: str
    method: str
    owner_team: str
    consumers: List[str]
    sla_ms: int  # レスポンスタイムSLA
    priority: Priority
 
    def validate_sla(self, actual_ms: int) -> bool:
        """SLA準拠の確認"""
        return actual_ms <= self.sla_ms
 
    def to_openapi(self) -> dict:
        """OpenAPI形式で出力"""
        return {
            'path': self.endpoint,
            'method': self.method,
            'x-owner': self.owner_team,
            'x-consumers': self.consumers,
            'x-sla-ms': self.sla_ms
        }
 
# 使用例
contracts = [
    APIContract(
        endpoint="/api/v1/users",
        method="GET",
        owner_team="user-team",
        consumers=["order-team", "notification-team"],
        sla_ms=200,
        priority=Priority.HIGH
    ),
    APIContract(
        endpoint="/api/v1/orders",
        method="POST",
        owner_team="order-team",
        consumers=["payment-team", "inventory-team"],
        sla_ms=500,
        priority=Priority.CRITICAL
    )
]

シナリオ4: パフォーマンスクリティカルなシステム

状況: ミリ秒単位のレスポンスが求められるシステム

最適化ポイント:

  1. キャッシュ戦略(L1: インメモリ、L2: Redis、L3: CDN)
  2. 非同期処理の活用
  3. コネクションプーリング
  4. クエリ最適化とインデックス設計
最適化手法 効果 実装コスト 適用場面
インメモリキャッシュ 頻繁にアクセスされるデータ
CDN 静的コンテンツ
非同期処理 I/O待ちが多い処理
DB最適化 クエリが遅い場合
コード最適化 低-中 CPU律速の場合

8. FAQ

Q1: Rustの学習曲線は本当に急峻ですか?

A: はい、特に所有権・ライフタイムの概念は他言語にない独自のものです。しかし一度習得すれば、コンパイラが多くのバグを事前に防いでくれるため、デバッグ時間が大幅に減少します。多くの開発者が「3-6ヶ月で生産性が上がり始める」と報告しています。

学習のコツ:

  • まずは The Rust Programming Language(公式Book)を通読する
  • Rustlings の演習で手を動かす
  • コンパイラのエラーメッセージを丁寧に読む(Rustのエラーメッセージは非常に親切)
  • 最初は clone() を多用しても構わない。動くコードを書いてから最適化する

Q2: RustはどんなプロジェクトにGo向きですか?

A: 以下のようなケースでRustが特に適しています:

  • パフォーマンスが最重要(ゲームエンジン、ブラウザエンジン)
  • メモリ安全が必須(OS、組み込み、セキュリティツール)
  • WebAssembly(Wasmとの親和性が高い)
  • CLIツール(シングルバイナリ配布が容易)
  • 高スループットなネットワークサービス(プロキシ、ロードバランサ)

逆に、以下のケースではGoの方が適している場合もあります:

  • 素早いプロトタイピングが必要
  • チームの学習コストを最小化したい
  • マイクロサービスの量産

Q3: Rustにガベージコレクタがないのに、なぜ安全なのですか?

A: Rustは「所有権システム」でメモリを管理します。各値には唯一の「所有者」が存在し、所有者がスコープを抜けると自動的にメモリが解放されます(Drop トレイト)。コンパイラが借用規則を静的に検証するため、ダングリングポインタやダブルフリーが発生しません。

Q4: Rustのコンパイルが遅い理由は何ですか?

A: Rustのコンパイルが遅い主な理由は:

  1. 借用チェック: 所有権とライフタイムの検証は計算コストが高い
  2. 単態化: ジェネリクスを使用すると、具体的な型ごとにコードが生成される
  3. LLVM最適化: 高品質な最適化には時間がかかる
  4. リンク時最適化 (LTO): リリースビルドで有効な場合、特に遅くなる

改善策として cargo check(バイナリ生成をスキップ)の使用、増分コンパイルの活用、sccache の導入などがある。

Q5: RustとC++の違いを一言で言うと?

A: 「C++は正しいコードも危険なコードも書ける。Rustは危険なコードをコンパイラが拒否する。」

C++はプログラマに全ての自由を与えるが、その結果としてメモリ安全性のバグが発生しうる。Rustはコンパイラが安全性を保証するため、C++で長年問題となってきたメモリ安全性のバグの約70%(Microsoft Researchの調査)をコンパイル時に防止できる。

Q6: Rustで Web 開発はできますか?

A: はい。バックエンドには Actix Web、Axum、Rocket などの成熟したフレームワークがあります。フロントエンドには WebAssembly を活用した Yew、Leptos、Dioxus などのフレームワークがあります。また、Tauri を使えばデスクトップアプリケーションも開発できます。

// Axum を使ったシンプルな Web サーバー
use axum::{routing::get, Router};
 
async fn hello() -> &'static str {
    "Hello, World!"
}
 
#[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();
    axum::serve(listener, app).await.unwrap();
}

9. 実践的な開発のヒント

9.1 コンパイラとの付き合い方

Rustのコンパイラ(rustc)は非常に厳格だが、そのエラーメッセージは極めて親切である。エラーメッセージに含まれる「help:」や「note:」の行を丁寧に読むと、ほとんどの場合で解決策が提示される。

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:4:20
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`
3 |     let s2 = s1;
  |              -- value moved here
4 |     println!("{}", s1);
  |                    ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl`
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

9.2 推奨する開発ツール

ツール 用途 インストール
rust-analyzer IDE サポート (LSP) VS Code 拡張等
clippy リンター rustup component add clippy
rustfmt フォーマッタ rustup component add rustfmt
cargo-watch ファイル変更時に自動ビルド cargo install cargo-watch
cargo-expand マクロ展開の確認 cargo install cargo-expand
cargo-audit セキュリティ脆弱性検査 cargo install cargo-audit
cargo-flamegraph パフォーマンスプロファイリング cargo install flamegraph
bacon 軽量なビルドウォッチャー cargo install bacon

9.3 よく使うデザインパターン

// ビルダーパターン
struct ServerConfig {
    host: String,
    port: u16,
    max_connections: u32,
    timeout_ms: u64,
}
 
struct ServerConfigBuilder {
    host: String,
    port: u16,
    max_connections: u32,
    timeout_ms: u64,
}
 
impl ServerConfigBuilder {
    fn new() -> Self {
        Self {
            host: "localhost".to_string(),
            port: 8080,
            max_connections: 100,
            timeout_ms: 30000,
        }
    }
 
    fn host(mut self, host: &str) -> Self {
        self.host = host.to_string();
        self
    }
 
    fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }
 
    fn max_connections(mut self, max: u32) -> Self {
        self.max_connections = max;
        self
    }
 
    fn timeout_ms(mut self, timeout: u64) -> Self {
        self.timeout_ms = timeout;
        self
    }
 
    fn build(self) -> ServerConfig {
        ServerConfig {
            host: self.host,
            port: self.port,
            max_connections: self.max_connections,
            timeout_ms: self.timeout_ms,
        }
    }
}
 
fn main() {
    let config = ServerConfigBuilder::new()
        .host("0.0.0.0")
        .port(3000)
        .max_connections(1000)
        .build();
 
    println!("サーバー: {}:{}", config.host, config.port);
}
// ニュータイプパターン
struct Email(String);
struct UserId(u64);
 
impl Email {
    fn new(email: &str) -> Result<Self, String> {
        if email.contains('@') {
            Ok(Email(email.to_string()))
        } else {
            Err("無効なメールアドレス".to_string())
        }
    }
 
    fn as_str(&self) -> &str {
        &self.0
    }
}
 
fn send_email(to: &Email, subject: &str) {
    println!("送信先: {}, 件名: {}", to.as_str(), subject);
}
 
fn main() {
    let email = Email::new("user@example.com").unwrap();
    send_email(&email, "テスト");
    // send_email(&"invalid", "テスト"); // コンパイルエラー!型が違う
}

FAQ

Q1: このトピックを学ぶ上で最も重要なポイントは何ですか?

実践的な経験を積むことが最も重要です。理論だけでなく、実際にコードを書いて動作を確認することで理解が深まります。

Q2: 初心者がよく陥る間違いは何ですか?

基礎を飛ばして応用に進むことです。このガイドで説明している基本概念をしっかり理解してから、次のステップに進むことをお勧めします。

Q3: 実務ではどのように活用されていますか?

このトピックの知識は、日常的な開発業務で頻繁に活用されます。特にコードレビューやアーキテクチャ設計の際に重要になります。


10. まとめ

項目 要点
設計哲学 安全性・速度・並行性を同時達成
メモリ管理 所有権システム(GCなし)
型システム 強い静的型付け + ジェネリクス + トレイト
ツールチェーン rustup / cargo / clippy / rustfmt
エコシステム crates.io に 15万+ のクレート
用途 システム、Web、CLI、Wasm、組み込み
Edition 後方互換を保ちつつ段階的に進化
テスト ユニットテスト、統合テスト、ドキュメントテストが標準装備
エラー処理 Result/Option による型安全なエラーハンドリング
並行処理 Send/Sync トレイトでコンパイル時にデータ競合を防止

次に読むべきガイド


参考文献

  1. The Rust Programming Language (公式Book) -- https://doc.rust-lang.org/book/
  2. Rust by Example -- https://doc.rust-lang.org/rust-by-example/
  3. Rust Reference -- https://doc.rust-lang.org/reference/
  4. Rustlings (演習) -- https://github.com/rust-lang/rustlings
  5. Rust Foundation -- https://foundation.rust-lang.org/
  6. Rust API Guidelines -- https://rust-lang.github.io/api-guidelines/
  7. The Rustonomicon (unsafe Rust) -- https://doc.rust-lang.org/nomicon/
  8. Rust Playground -- https://play.rust-lang.org/
  9. This Week in Rust -- https://this-week-in-rust.org/
  10. Rust Design Patterns -- https://rust-unofficial.github.io/patterns/