Skip to content
Published on

Rustとシステムプログラミングの帰還 完全攻略 — Ownership, Borrowing, Lifetime, Trait, Async, Tokio, Cargo, WebAssembly (2025)

Authors

"In Rust, we don't trust the programmer. We trust the compiler — and the programmer, trained by the compiler, becomes trustworthy." — Niko Matsakis (Rust core team)

2024年2月、ホワイトハウスがC/C++からメモリ安全言語への移行を公式に推奨。2025年2月、LinuxカーネルがRustドライバをメインツリーに統合。Stack Overflow「最も愛される言語」9年連続1位。これはファンダムではなく、ソフトウェア産業全体の構造的転換が進んでいる証だ。

「難しい」という評判にもかかわらず、なぜRustはこれほど影響力を持つのか。本稿ではRustの誕生から2025年のエコシステムまで、「Rustを理解する」とは何かを解説する。


1. Rustの誕生 — Mozillaの贈り物

Graydon Hoareの個人プロジェクト (2006)

  • カナダのエンジニア、自宅のエレベータが故障する経験から着想
  • 「壊れないソフトウェアのための言語」を目指す
  • 初期はOCaml影響の強い実験言語

Mozilla公式プロジェクト (2010)

  • MozillaがGraydonを雇用しRustに投資
  • Servo — Rustで書くブラウザエンジン実験
  • 2015年に1.0リリース

Servo → Firefox Quantum (2017)

  • ServoのCSS/レンダリングエンジン(Stylo, WebRender)をFirefoxに移植
  • 初の大規模実戦投入で成功
  • 性能2倍、バグ減少

財団独立 (2021)

  • Mozilla財政難でRustチーム解散
  • AWS, Google, Microsoft, Huawei, Metaが主導しRust Foundation設立
  • ガバナンス分散

2025 — 遍在

  • Linuxカーネル (Rust for Linux)
  • Windowsカーネル (Microsoftが移植中)
  • ブラウザ(Firefox, Servo復活)、JSツール(swc, oxc, Turbopack)
  • ランタイム(Deno)、DB(TiKV, Qdrant, SurrealDB)
  • ブロックチェーン(Solana, Polkadot)

2. Ownership — GCなしのメモリ安全

問題設定

  • C/C++: 手動管理 → use-after-free, double-free, memory leak
  • Java/Go: GC → 予測不能な遅延とメモリオーバーヘッド
  • 両方を避ける第三の道は?

Rustの3原則

  1. 各値は正確に1つのownerを持つ
  2. ownerがスコープを抜けると値はdropされる
  3. ownerが変われば以前のownerは使えない

基本例

fn main() {
    let s = String::from("hello");
    let t = s;  // sの所有権がtに移動
    // println!("{}", s);  // エラー! sはもう無効
    println!("{}", t);  // OK
}  // tがスコープを抜ける -> drop

Copy vs Move

  • スタック値 (i32, bool, char): Copy
  • ヒープ割り当て (String, Vec): Move
let x = 5;
let y = x;  // Copy
println!("{} {}", x, y);  // OK

let s1 = String::from("hi");
let s2 = s1;  // Move
// println!("{}", s1);  // エラー!

Clone — 明示的コピー

let s1 = String::from("hi");
let s2 = s1.clone();  // ヒープまで複製
println!("{} {}", s1, s2);  // OK

.clone()を書くことでコストがコード上に見える。暗黙の性能コストは発生しない。


3. Borrowing — 借用の芸術

なぜ必要か

fn print_len(s: String) { println!("{}", s.len()) }

let s = String::from("hi");
print_len(s);  // 所有権が移動
// sはここで使えない

不便なので**参照(&)**が導入された。

Immutable Borrow

fn print_len(s: &String) { println!("{}", s.len()) }

let s = String::from("hi");
print_len(&s);
println!("{}", s);  // OK、所有権保持

Mutable Borrow

fn add_world(s: &mut String) {
    s.push_str(" world")
}

let mut s = String::from("hi");
add_world(&mut s);
println!("{}", s);  // "hi world"

中核ルール — 「共有xor可変」

ある瞬間に:

  • N個のimmutable borrow、または
  • 1個のmutable borrow
  • 両立不可
let mut s = String::from("hi");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;  // エラー! 既にimmutable borrow存在

なぜこのルールか

  • データ競合が原理的に起きない — 共有されるなら変更されない
  • コンパイル時証明 — ランタイムコスト0
  • Rustの「fearless concurrency」の基盤

4. Lifetime — 参照の有効期間

問題

fn dangling() -> &String {
    let s = String::from("hi");
    &s
}  // sがdropされると&sはdangling

Cではセグフォ、Rustではコンパイルエラー

Lifetimeアノテーション

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

'aは関数内で共通のLifetime。戻り値はxとyの短い方のLifetimeを持つ。

Lifetime Elision — 省略規則

多くは明示不要。

fn first_word(s: &str) -> &str { /* ... */ }
// 内部: fn first_word<'a>(s: &'a str) -> &'a str

3つの規則:

  1. 各参照パラメータは自分のLifetimeを持つ
  2. パラメータが1つならreturnのLifetime = そのパラメータ
  3. &selfがある場合、returnのLifetime = selfのLifetime

'static

let s: &'static str = "hello";  // 文字列リテラル

プログラム全期間有効。濫用されがち。

Lifetimeが難しい理由

  • 最初は「なぜ必要?」と感じる
  • 複雑な状況(self-referential struct等)で理解が必要
  • 解法: 最初から書かず、コンパイラに要求されたら学ぶ

5. Trait System — Rustの中核抽象化

Trait = Interface + Mixin + Typeclass

trait Greet {
    fn hello(&self) -> String;
    fn goodbye(&self) -> String {  // デフォルト実装
        String::from("bye")
    }
}

struct Kim { name: String }

impl Greet for Kim {
    fn hello(&self) -> String {
        format!("こんにちは、私は{}", self.name)
    }
}

Genericとtrait bounds

fn greet_all<T: Greet>(people: &[T]) {
    for p in people {
        println!("{}", p.hello());
    }
}

T: Greetは「TはGreetを実装する任意の型」。Java genericsの上限境界と似るが更に強力。

impl Trait (2018)

fn make_greeter() -> impl Greet {
    Kim { name: String::from("金") }
}

「何らかのGreet実装を返す」 — 具体型を隠蔽。

Trait Object — dyn Trait

fn greet_all(people: &[Box<dyn Greet>]) {
    for p in people {
        println!("{}", p.hello());
    }
}

vtableベースの動的ディスパッチ。C++仮想関数に似るが、dynキーワードでコストが明示される。

Orphan Rule

traitか型のいずれかが現在のcrate所有であることがimplの条件。複数crateからの競合実装を防ぐ。

有名なTrait

  • Debug{:?}出力
  • Clone, Copy — コピー
  • Iterator — for構文
  • Drop — デストラクタ
  • From/Into — 変換
  • Deref*演算子オーバーロード

6. Zero-cost Abstractions — Bjarneの約束、Rustの実現

哲学

"What you don't use, you don't pay for. What you do use, you couldn't hand-code better." — Bjarne Stroustrup

例1 — Iterator

let sum: i32 = (1..=100)
    .filter(|n| n % 2 == 0)
    .map(|n| n * 2)
    .sum();

高レベル関数型チェインもコンパイル後のアセンブリは手書きforループと同一。LLVM最適化が抽象化を完全に除去。

例2 — Option<T>

let x: Option<i32> = Some(5);
let y = x.unwrap_or(0);

Cのnullチェックより安全だがランタイムコストは0。Option自体がtagged unionにコンパイルされる。

例3 — Generic vs dyn Trait

// Generic — コンパイル時monomorphization
fn static_greet<T: Greet>(x: T) { /* ... */ }  // インライン可

// Trait object — ランタイムvtable
fn dynamic_greet(x: &dyn Greet) { /* ... */ }  // ポインタ参照

開発者がコストを選択する。


7. Error Handling — panicではなくResult

Result<T, E>

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("zero division"))
    } else {
        Ok(a / b)
    }
}

match divide(10, 2) {
    Ok(v) => println!("{}", v),
    Err(e) => eprintln!("{}", e),
}

?演算子 — エラー伝播

fn do_stuff() -> Result<(), MyError> {
    let data = read_file("x.txt")?;
    let parsed = parse(&data)?;
    save(parsed)?;
    Ok(())
}

try/catchなしでもエラー伝播が綺麗。呼び出し側がResultを返す必要がある。

panic! — 異常時のみ

  • 配列out-of-bounds、unwrap失敗等
  • 復旧不能なロジックエラー
  • 実務ではほぼ発生しないのが理想

thiserroranyhow

  • thiserror — ライブラリ向け、明示的エラーenum
  • anyhow — アプリ向け、エラー合成が楽
use anyhow::{Result, Context};

fn load_config() -> Result<Config> {
    let s = std::fs::read_to_string("config.yml")
        .context("config.yml 読み込み失敗")?;
    Ok(serde_yaml::from_str(&s)?)
}

8. Async/AwaitとTokio

async/awaitの非同期

async fn fetch_user(id: u64) -> Result<User> {
    let resp = reqwest::get(format!("/users/{}", id)).await?;
    Ok(resp.json().await?)
}

#[tokio::main]
async fn main() {
    let user = fetch_user(1).await.unwrap();
    println!("{:?}", user);
}

asyncはFutureを作る

async fnを呼ぶだけでは何も起こらない — Futureを返すだけ。ランタイムがpollして初めて実行される。

ランタイム

  • Tokio — 事実上の標準、マルチスレッド、I/O最適化
  • async-std — 標準ライブラリ風
  • smol — 軽量
  • monoio / glommio — io_uringベース、thread-per-core

Tokioの強み

  • 数百万の同時接続
  • tokio::spawnで非同期タスク
  • tokio::select!, join!で並列制御
  • 豊富なエコシステム (hyper, tonic, axum, tower)

JSのasync/awaitとの違い

観点JSRust
デフォルト実行即開始 (Promise)awaitするまで動かない
ランタイムビルトインライブラリ
Promise<T>impl Future<Output=T>
キャンセル暗黙的で難しいdropで自動
性能V8チューニング限界ネイティブ

Async Trait — 2024年ついに

長らくasync fn in traitはワークアラウンドが必要だった。Rust 1.75 (2023年12月)から安定化が始まり2024-2025で完成。


9. Cargo — パッケージマネージャの理想形

一つのツールで全部

cargo new myapp          # 新規プロジェクト
cargo build              # ビルド
cargo run                # 実行
cargo test               # テスト
cargo bench              # ベンチマーク
cargo doc                # ドキュメント生成
cargo fmt                # フォーマット (rustfmt)
cargo clippy             # リント
cargo publish            # crates.io公開

依存管理、ビルド、テスト、ドキュメント、ベンチが標準コマンド1つずつ。Python/Node生態系の断片化(pip/poetry, npm/yarn/pnpm)と対照的。

Cargo.toml

[package]
name = "myapp"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"

[dev-dependencies]
criterion = "0.5"

Features — 選択的機能

[features]
default = ["tokio"]
tokio = ["dep:tokio"]
async-std = ["dep:async-std"]

同一crateで複数ランタイムをサポート等。条件コンパイルと組み合わせ。

Workspace — モノレポ標準対応

[workspace]
members = ["crates/*"]

複数crateを1リポジトリで管理、依存バージョン統一。

crates.io

  • 中央リポジトリ
  • 17万以上のcrate
  • cargo publish一行
  • バージョンのyankは可能、削除は不可

10. 有名なRustプロジェクト 2025

インフラ

  • ripgrep — grep代替、圧倒的高速
  • fd — find代替
  • bat — シンタックスハイライト付きcat
  • exa/eza — ls改良
  • starship — シェルプロンプト
  • helix — エディタ

コンテナ/クラウド

  • containerd plugin
  • firecracker (AWS Lambda) — 一部Rust
  • Linkerd2-proxy — service meshプロキシ
  • kube-rs — Rust K8sクライアント

フロントエンドツール

  • swc — Babel代替
  • Turbopack — Webpack後継
  • Rspack — Webpack互換
  • Biome (fka Rome) — lint+format
  • oxc — 新JSツールチェイン

ランタイム/DB

  • Deno — Node代替
  • Qdrant — ベクトルDB
  • SurrealDB — マルチモデル
  • TiKV — 分散KV
  • InfluxDB 3 — 時系列 (Rustで再実装)
  • Neon — サーバレスPostgres (一部Rust)

Web/サーバ

  • axum — Tokioチーム製Webフレームワーク
  • actix-web — 高性能Web
  • rocket — 開発者フレンドリー
  • tower — ミドルウェアエコシステム

11. Rustで書くフロントエンド — WebAssembly

wasm-bindgen

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("こんにちは、{}!", name)
}

JSからgreet("Kim")で呼び出せる。

Leptos — Solid.jsスタイル

#[component]
fn Counter() -> impl IntoView {
    let (n, set_n) = create_signal(0);
    view! {
        <button on:click=move |_| set_n.update(|v| *v += 1)>
            "カウント: " {n}
        </button>
    }
}
  • Fine-grained reactivity
  • SSR + Hydration対応
  • バンドルが非常に小さい

Dioxus — Reactスタイル

fn Counter(cx: Scope) -> Element {
    let n = use_state(cx, || 0);
    render! {
        button { onclick: move |_| n.set(n + 1), "{n}" }
    }
}
  • React風JSXマクロ
  • Web, Desktop, Mobile, TUIクロスプラットフォーム
  • 2024-2025で急成長

Yew

  • 最も古いRustフロントエンドフレームワーク
  • Virtual DOMベース
  • 保守的で安定

現実

  • プロダクション採用はまだ小規模
  • React/Vue全面代替より特定の性能ボトルネックの置換用途
  • Figma, Photopea等の複雑クライアントでWasmモジュールとして利用

12. Rust for Linux — 2025年の勝利

長い道のり

  • 2020: Linus Torvaldsが「検討の価値あり」と発言
  • 2021: メーリングリストで正式提案
  • 2022: カーネル6.1に初期Rust対応
  • 2024: 一部ドライバ (Apple GPU, Nvidiaオープンドライバ一部)
  • 2025: 主要ドライバのマージ

インパクト

  • カーネルCVEの70%がメモリ安全バグ
  • Rustがこれを根本から遮断
  • MicrosoftもWindowsカーネル一部をRust移植中

摩擦

  • 一部C保守者の抵抗 (「コードでなく部族主義」)
  • 2024年に著名メンテナが辞任
  • 言語移行は技術だけでなく文化の問題

Asahi Linuxチーム

  • Apple Silicon Linux — 新規ドライバのほぼ全てがRust
  • 実戦投入として最も急進的

13. 政府と規制 — Rustの義務化

ホワイトハウスONCDレポート (2024年2月)

"Memory-safe languages, including Rust, Go, C#, Java, Swift, Python, and JavaScript, offer...the most comprehensive approach to eliminate an entire class of vulnerabilities."

国防総省、NASA、航空

  • 新規安全必須システムにRust推奨
  • C/C++の監査負担に対しRust支持が増加

Ferrocene — 認証済みRustコンパイラ

  • ISO 26262 (自動車機能安全)
  • DO-178C (航空ソフトウェア)
  • Ferrous Systemsが商用サポート

2025現在

  • Infineon, Volvo, Boeingでパイロット
  • Rustが安全必須組込みの主流候補に浮上

14. Rustを学ぶ最短ルート

ステージ1 (1-2週間)

  1. The Rust Book (無料、公式)を完読
  2. rustlingsの課題100個
  3. 小さなCLI作成 (argh/clap)

ステージ2 (2-4週間)

  1. OwnershipとBorrowingに慣れる
  2. Result?を体得
  3. Iteratorチェインを習熟
  4. Trait設計を練習

ステージ3 (1-3か月)

  1. Async/Tokioでネットワークアプリ
  2. serdeでJSON/YAML
  3. エラー型設計 (thiserror/anyhow)
  4. 小規模実戦プロジェクトをデプロイ

ステージ4 (3か月以上)

  1. unsafeを理解
  2. マクロ (宣言的/手続き的)
  3. 高度なLifetime
  4. FFI (Cとの連携)
  5. オープンソースcrateに貢献

無料リソース

  • The Rust Book (doc.rust-lang.org/book)
  • Rust by Example
  • Rustlings (github.com/rust-lang/rustlings)
  • Rust for JavaScript Developers
  • Zero to Production in Rust (Luca Palmieri、有料)

15. アンチパターン TOP 10

  1. .clone()濫用 — コンパイルは通るが性能浪費
  2. .unwrap()乱用 — 本番でpanic
  3. Rc<RefCell<T>>で借用規則回避 — Rustの長所を放棄
  4. 何でもasync — CPUバウンドには不要
  5. Lifetime過剰明示 — elisionで大半は処理される
  6. dyn Traitを性能考慮なしに — genericの方が速いことが多い
  7. マクロ濫用 — コンパイル時間爆発、デバッグ地獄
  8. Box<dyn Error>濫用 — 細やかなエラー型を放棄
  9. unsafe不要なのにunsafe — 安全性放棄
  10. Rustスタイルでない他言語のパラダイム — snake_case、trait中心を守る

16. Rustを賢く使うチェックリスト

  • cargo clippyをCIで実行 — コーディングスタイル統一
  • cargo fmtをpre-commit — 自動フォーマット
  • #![deny(unsafe_code)]をライブラリのデフォルトに
  • エラー型を設計 — thiserror + anyhowの組み合わせ
  • 公開APIドキュメント — ///コメント + doctest
  • ベンチマーク — criterionで回帰防止
  • Miriテスト — unsafe検証
  • cargo-deny — ライセンス/脆弱性監査
  • MSRV明示 — Minimum Supported Rust Version
  • CIで複数プラットフォームテスト — Linux/Mac/Windows
  • Feature flag設計 — 選択的依存
  • Async vs Syncの判断 — 本当にasyncが必要か

おわりに — 「システムプログラミングのルネサンス」

30年間、システムプログラミングはC/C++の独占だった。誰もこの独占が崩れるとは信じていなかった。2015年にRust 1.0が出た時も、多くの専門家は「物好きの話」と片付けた。

10年が経った今、Linuxカーネルが、ホワイトハウスが、AzureとAWSがRustへ移行している。これは革命というより世代交代に近い。Cは消えないが、新しいシステムコードは次第にRustで生まれる

Rustが本当に贈ってくれるのは速度ではない。それは**「コンパイラと協調して正しいプログラムを書く経験」**である。borrow checkerと格闘するうちに、メモリ、並行性、生存期間に対する直観が得られる。その直観は他の言語に戻っても、あなたと共にある。

"Writing Rust is sometimes like being shouted at by an expert compiler. When you finally agree with it, your code is bulletproof." — Amos Wenger (fasterthanli.me)


"Rust is for when you need to be fast. Go is for when you need to be done. Python is for when you need both. TypeScript is for when you had a Python and want to sleep at night." — Anonymous HN comment (2024)