Rust
目次
主要項目のみを表示しています。詳細な小見出しは本文内で確認できます。
- 概要
- 1. Rustとは何か
- 2. ツールチェイン(rustc / cargo)
- 3. 型と変数
- 4. 所有権・借用・ライフタイム
- 5. 構造体と列挙型
- 6. パターンマッチングと制御フロー
- 7. トレイトとジェネリクス
- 8. エラーハンドリング(Result / Option / ?)
- 9. コレクションとイテレータ
- 10. スマートポインタ(Box / Rc / Arc / RefCell)
- 11. 並行処理(Send / Sync / Mutex)
- 12. async / await
- 13. マクロ
- 14. unsafeとFFI
- 15. モジュール・クレート・workspace
- 16. テストとドキュメント
- 17. Rust 2018/2021 Editionと新機能
- 18. よくある落とし穴FAQ
- 19. 図解: 所有権と借用
- 20. 学習ロードマップ(30日)
- 21. 用語集
- 発展: 所有権と型の詳細
- 22. 所有権・借用・ライフタイム詳解
- 23. トレイト深掘り
- 24. ジェネリクスとモノモルフィゼーション
- 25. エラーハンドリング深掘り
- 26. async / await完全版
- 27. unsafe Rust
- 28. マクロ深掘り
- 29. パフォーマンスチューニング
- 30. エコシステムと実践的な組み合わせ
- 30. エコシステム主要クレート
- 実践: 内部構造とCI
- 応用: Webと組み込み
- 上級: Pinとメタプログラミング
- まとめ
- 参考文献
概要
まず、この章の中心構造を図で確認します。細部に入る前に、どの概念がどこへつながるかをつかむための地図です。
コード例は、そのまま写すためだけのものではありません。直前の本文で「何を確かめる例か」を押さえ、直後の説明で「どの性質が見えるか」を確認してください。実務では、ここに入力の境界、失敗時の挙動、依存する実行環境を足して読むと判断しやすくなります。
Rustは、所有権と型システムによって、メモリ安全性と性能を同時に狙うシステムプログラミング言語です。
このページでは、所有権、借用、ライフタイム、Result/Option、trait、async、unsafe、Cargoを、Rustの安全性モデルと結びつけて整理します。
1. Rustとは何か
Rustは、「メモリ安全・並行安全・高速」 を同時に実現するシステムプログラミング言語。所有権(ownership) という独創的な仕組みで、GCなしに メモリ安全性をコンパイル時に保証します。
主な用途:
- システムプログラミング: OS(Redox)、組み込み、Linuxカーネル(Rustモジュール6.1+)
- Webバックエンド: actix-web、axum、Rocket
- WebAssembly: ブラウザで動くRust(wasm-bindgen)
- CLIツール: ripgrep、fd、bat、starship
- 暗号・ブロックチェーン: Solana、Polkadot
- インフラ: TiKV、Vector、firecracker
- ゲーム: Bevy、Servo
- 言語処理系: Deno(一部)、Ruff、Biome、SWC
「Stack Overflowの最も愛される言語」7年連続で1位を獲得した言語でもあります。
1-1. Rustの歴史
Mozillaの社内プロジェクトとして誕生(2006)
2006年、MozillaのGraydon Hoareが個人プロジェクトとして開始。2009年にMozillaがスポンサー、2010年に正式公開。FirefoxのレンダリングエンジンServoをRustで書く野望から始まりました。
2006 Graydon Hoareが開発開始
2010 Mozillaが公式公開
2015 Rust 1.0リリース(言語安定化)
2018 Rust 2018 Edition(asyncプレビュー)
2019 async/await安定化
2021 Rust 2021 Edition、Rust Foundation設立
2022 GAT安定化
2023 Linuxカーネル6.1でRustが採用される
2024 Rust 1.75〜 async fn in trait安定化
Rust Foundation(2021)
Mozillaからスピンアウトし、Rust Foundation が独立組織として運営。AWS、Google、Microsoft、Meta、Huaweiが創設メンバー。
Linuxカーネルへの採用
2022年、Linuxカーネル6.1 でRustが公式言語として採用。システムプログラミングの世界でC以外の選択肢が認められた歴史的事件。
1-2. 設計哲学
“A language empowering everyone to build reliable and efficient software.”
Rustの核心は 「Trade-offsを再考する」こと:
従来:
C/C++ 高速だがメモリ安全性は手作業
GC言語 安全だがランタイムコスト
Rust:
高速 + メモリ安全 + GCなし
── 所有権という独創的な型システムで実現
所有権の3原則
1. 各値には1つの所有者がいる
2. 同時に有効な所有者は1つだけ
3. 所有者がスコープを抜けると値はドロップされる
これにより コンパイル時にメモリ安全性を保証。GCは不要、malloc/free も不要、new/delete も不要。
Fearless Concurrency
「並行処理を恐れない」のスローガン。所有権 + Send/Sync トレイトにより、データレースをコンパイル時に防止します。
1-3. このセクションのまとめ
- 2006年Graydon Hoare、Mozillaで誕生
- 2015年Rust 1.0、2021年Rust Foundation
- 「メモリ安全 + 高速 + GCなし」を所有権で実現
- Linuxカーネル採用(6.1+)、業界の信認
- Stack Overflowで7年連続「最も愛される言語」
2. ツールチェイン(rustc / cargo)
Rustは 「ツールが洗練されている」のが大きな魅力。rustup、cargo、rustfmt、clippy が標準で揃い、初日から快適に書けます。
2-1. rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update # ツールチェイン更新
rustup default stable # stable版に
rustup install nightly # nightly追加
rustup target add wasm32-unknown-unknown # WASMターゲット
rustup は 複数バージョン・複数ターゲットを統一管理するツール。
2-2. cargo(パッケージマネージャ + ビルドツール)
cargo new myapp # 新規プロジェクト
cargo build # ビルド
cargo build --release # 最適化ビルド
cargo run # ビルドして実行
cargo test # テスト
cargo bench # ベンチマーク(nightly)
cargo doc --open # ドキュメント生成
cargo add serde # 依存追加
cargo update # 依存更新
cargo publish # crates.ioへ公開
cargo fmt # フォーマット
cargo clippy # 静的解析(lint)
cargo は 依存管理・ビルド・テスト・配布をすべて統合。Goの go 同様、サードパーティツールを追加せずに完結します。
2-3. Cargo.toml
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
[dev-dependencies]
criterion = "0.5"
[profile.release]
opt-level = 3
lto = true
Cargo.lock で依存バージョンを固定。アプリではcommit、ライブラリではgitignoreする慣習。
2-4. このセクションのまとめ
rustup: 複数バージョン管理
cargo: ビルド・依存・テスト・配布の統合
Cargo.toml: マニフェスト
clippy: lint必須、CIで実行
rustfmt: 標準フォーマッタ
3. 型と変数
Rustは 静的型付け + 強力な型推論。型を明示しなくても多くの場合動きます。
3-1. プリミティブ型
| 型 | 説明 |
|---|---|
i8 i16 i32 i64 i128 isize |
符号付き整数 |
u8 u16 u32 u64 u128 usize |
符号なし |
f32 f64 |
浮動小数点 |
bool |
true / false |
char |
4 byteのUnicodeスカラ値 |
() |
unit型(Cのvoid相当) |
&str |
文字列スライス(不変参照) |
String |
所有された文字列(ヒープ) |
Rustの char は 4バイトで、絵文字や日本語を1つのcharで表せます(JavaのcharはUTF-16で2バイト)。
3-2. letとmut
let x = 10; // イミュータブル(再代入不可)
let mut y = 20; // ミュータブル
y = 30; // OK
x = 100; // エラー!
Rustではイミュータブルがデフォルト。これが言語の安全性の基盤になっています。
シャドーイング
let x = 10;
let x = x + 1; // 別の変数として再宣言(型変更も可)
let x = x.to_string();
「同じ名前で別の変数」を作る。型を変えるときに便利。
3-3. 型推論
let x = 5; // i32(デフォルト)
let y = 5.0; // f64
let z: u32 = 5; // 型を明示
let v = vec![1, 2, 3]; // Vec<i32>
let v: Vec<u8> = vec![1, 2, 3]; // 型を要請
Rustの型推論は強力で、関数のシグネチャ以外ではほぼ書かなくて済む。
3-4. このセクションのまとめ
- イミュータブルがデフォルト(mutで可変)
- charは4バイトUnicode
- &str(借用)とString(所有)の区別
- 強力な型推論(注釈は最小限)
- シャドーイングで同名再宣言可能
4. 所有権・借用・ライフタイム
Rustの核心。これが分かればRustの半分は理解できたと言えます。
4-1. 所有権の3原則
1. 各値には1つの所有者がいる
2. 同時に有効な所有者は1つだけ
3. 所有者がスコープを抜けると値はドロップされる
{
let s = String::from("hello"); // sが所有者
// 使用
} // ここでsがドロップされ、メモリ解放
4-2. ムーブ(move)
let s1 = String::from("hello");
let s2 = s1; // s1からs2へ「ムーブ」
println!("{}", s1); // エラー!s1はもう使えない
代入で 所有権が移動。これがRustの最も特徴的な挙動。
Copyトレイト
プリミティブ型(数値・bool・char)は Copy トレイトを実装しているので、ムーブではなく コピーされます。
let x = 10;
let y = x; // コピー
println!("{}", x); // OK、xは使える
4-3. 借用(borrow)
「所有権を渡さずに参照だけ貸す」のが借用。
fn len(s: &String) -> usize {
s.len()
}
let s = String::from("hello");
let n = len(&s); // & で借用
println!("{}", s); // OK、sはまだ所有している
ミュータブルな借用
fn add_world(s: &mut String) {
s.push_str(" world");
}
let mut s = String::from("hello");
add_world(&mut s);
借用のルール
1. 同時に複数の不変借用はOK
2. ミュータブル借用は1つだけ
3. 不変借用とミュータブル借用は同時に存在できない
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s; // OK、複数の不変借用
let r3 = &mut s; // エラー!不変借用がまだ生きている
これが データレースをコンパイル時に防ぐ仕組み。
4-4. ライフタイム
参照が どれくらい生きるかを表す注釈。多くの場合は推論されますが、関数シグネチャでは明示が必要なことがあります。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
'a は 「ライフタイムa」。「xとyのライフタイムが同じで、戻り値も同じライフタイム」と表明。
ダングリング参照を防ぐ
fn dangle() -> &String { // エラー!戻り値のライフタイムが不明
let s = String::from("hello");
&s // sはこの関数を抜けるとドロップされる
}
これが 「解放後アクセスをコンパイル時に防ぐ」仕組み。C/C++ の最大の安全性問題が解決されています。
4-5. このセクションのまとめ
所有権:
各値に1つの所有者
代入はムーブ(プリミティブはCopy)
スコープを抜けるとドロップ
借用:
&T不変借用 / &mut Tミュータブル借用
「複数の不変OR 1つのミュータブル」
ライフタイム:
参照の生存期間
通常は推論、関数シグネチャで明示することも
ダングリング参照を防ぐ
5. 構造体と列挙型
Rustの代数的データ型。struct でAND(積型)、**enum でOR(直和型)**を表現します。
5-1. struct
struct Point {
x: f64,
y: f64,
}
let p = Point { x: 1.0, y: 2.0 };
println!("{}, {}", p.x, p.y);
// タプルstruct
struct Pair(i32, i32);
let p = Pair(1, 2);
p.0; p.1;
// unit struct
struct Marker;
implブロック
impl Point {
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
fn distance_from_origin(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
let p = Point::new(3.0, 4.0);
p.distance_from_origin(); // 5.0
Self は「自分の型」のエイリアス。
5-2. enum
Rustの enum は タグ付き共用体(ADT)。Java/Cの弱いenumと違い、値を持てる。
enum Shape {
Circle(f64), // 半径
Rectangle { w: f64, h: f64 }, // 幅・高さ
Triangle(f64, f64, f64), // 3辺
}
let s = Shape::Circle(5.0);
match s {
Shape::Circle(r) => 3.14 * r * r,
Shape::Rectangle { w, h } => w * h,
Shape::Triangle(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s-a) * (s-b) * (s-c)).sqrt()
}
}
これが 代数的データ型(ADT)。HaskellやScalaのcase classに相当。Rustの最強の表現力の核。
5-3. Option とResult<T, E>
最も使われる組み込みenum。
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
null の代わりに Option、例外の代わりに Result。
fn find(name: &str) -> Option<&User> { ... }
fn parse(s: &str) -> Result<i32, ParseError> { ... }
5-4. このセクションのまとめ
struct:
名前付きフィールド / タプル / unit
implブロックでメソッド
enum:
ADT、各バリアントが値を持てる
matchで網羅的に分解
Option / Resultが組み込み
Self: 自分の型のエイリアス
6. パターンマッチングと制御フロー
Rustの match は 網羅性検証付きの強力なパターンマッチング。
6-1. match
match x {
0 => "zero",
1 | 2 => "one or two",
3..=10 => "small",
n if n < 0 => "negative",
_ => "other",
}
match は すべてのパターンを網羅していなければコンパイルエラー。enumと組み合わせるとbugを防ぎやすい。
let / if let / while let
let Some(x) = maybe_value else {
return; // Noneならreturn(Rust 1.65+)
};
if let Some(x) = maybe_value {
println!("{}", x);
}
while let Some(x) = iterator.next() {
println!("{}", x);
}
6-2. ifは式
let label = if x > 0 { "positive" } else { "non-positive" };
C/Javaの三項演算子に相当する書き方が if 式で可能。
6-3. ループ
loop { // 無限ループ
if done() { break; }
}
while x > 0 { x -= 1; }
for i in 0..10 { ... } // 0..9
for i in 0..=10 { ... } // 0..10
for x in vec { ... } // イテレータ
// loopは値を返せる
let result = loop {
if cond { break 42; }
};
6-4. このセクションのまとめ
match:
パターンマッチング、網羅性検証
if / whileもmatchと同じ流儀
let-else:
Noneを早期リターンで除外(1.65+)
ifは式:
let x = if cond { a } else { b };
ループ:
loop / while / for
loopはbreakで値を返せる
7. トレイトとジェネリクス
Rustの 抽象化機構の核。Javaのinterface + ジェネリクスに似ていますが、より強力で型安全。
7-1. trait
trait Greet {
fn greet(&self) -> String;
// デフォルト実装
fn shout(&self) -> String {
self.greet().to_uppercase()
}
}
struct Dog;
impl Greet for Dog {
fn greet(&self) -> String {
String::from("Woof!")
}
}
let d = Dog;
d.greet(); // "Woof!"
d.shout(); // "WOOF!"
traitは 「振る舞いの集合」。impl Trait for Type で実装。
7-2. ジェネリクス
fn max<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
max(1, 2);
max(1.5, 2.5);
max("a", "b");
T: PartialOrd で 「PartialOrd を実装している型」 という制約。
where句
fn process<T, U>(t: T, u: U) -> T
where
T: Display + Clone,
U: Debug,
{
...
}
複雑な制約は where 句で書くと読みやすい。
7-3. traitオブジェクト(dyn)
fn print_all(items: &[Box<dyn Greet>]) {
for item in items {
println!("{}", item.greet());
}
}
let items: Vec<Box<dyn Greet>> = vec![
Box::new(Dog),
Box::new(Cat),
];
dyn Trait で 動的ディスパッチ(virtual call相当)。ジェネリクス(静的)と使い分ける。
7-4. deriveマクロ
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point { x: i32, y: i32 }
主要なトレイトを 自動実装。
| derive | 効果 |
|---|---|
Debug |
{:?} で表示可能 |
Clone |
.clone() でコピー可能 |
Copy |
暗黙コピー(Cloneも必要) |
PartialEq/Eq |
== 比較 |
Hash |
HashMapキーに |
Default |
T::default() |
PartialOrd/Ord |
順序比較 |
7-5. このセクションのまとめ
trait:
振る舞いの契約、デフォルト実装可能
Java interface + Haskell type class
ジェネリクス:
fn f<T: Trait>(x: T) で型制約
where句で複雑な制約
dyn Trait:
動的ディスパッチ
ジェネリクスとの使い分け
derive:
Debug / Clone / PartialEq / Hash等を自動実装
8. エラーハンドリング(Result / Option / ?)
Rustには例外がありません。Result<T, E> と Option<T> で表現します。
8-1. ResultとOption
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("divide by zero"))
} else {
Ok(a / b)
}
}
match divide(10, 2) {
Ok(v) => println!("{}", v),
Err(e) => println!("error: {}", e),
}
fn first(v: &Vec<i32>) -> Option<&i32> {
if v.is_empty() { None } else { Some(&v[0]) }
}
8-2. ? 演算子
エラー伝播の糖衣構文。
fn read_config() -> Result<Config, Box<dyn Error>> {
let s = std::fs::read_to_string("config.toml")?; // Errならreturn
let cfg: Config = toml::from_str(&s)?;
Ok(cfg)
}
? は 「Errなら早期return、Okなら中身を取り出す」。Javaのchecked例外に近いが、より明示的。
8-3. unwrap / expect
let x: i32 = "42".parse().unwrap(); // 失敗でpanic
let x: i32 = "42".parse().expect("数値ではない");
「panicしてもいい場面」(テスト・小スクリプト・絶対に成功する変換)以外では避ける。
8-4. anyhow / thiserror
// アプリ側: anyhowで雑に
use anyhow::Result;
fn run() -> Result<()> { ... }
// ライブラリ側: thiserrorでカスタムエラー型
#[derive(thiserror::Error, Debug)]
enum MyError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("parse error")]
Parse,
}
「アプリは anyhow、ライブラリは thiserror」が現代の慣習。
8-5. panic
panic!("something bad");
panic は 回復不能なバグで使う。スタックを巻き戻して終了(catch_unwind で捕まえることはできるが推奨されない)。
8-6. このセクションのまとめ
- 例外なし、Result<T, E> とOption<T>
- ? でErr早期return
- unwrap/expectはpanic、注意して使う
- anyhow(アプリ)+ thiserror(ライブラリ)
- panicは回復不能なバグ用
9. コレクションとイテレータ
Rustのコレクションは Vec、String、HashMap、HashSet、BTreeMap が主要。イテレータが超強力で、関数型の操作が標準。
9-1. Vec / String / HashMap
let mut v = vec![1, 2, 3];
v.push(4);
v.len();
v[0];
let s = String::from("hello");
let s = format!("{} {}", "hello", "world");
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("a", 1);
m.get("a"); // Option<&i32>
m.entry("b").or_insert(0);
9-2. イテレータ
let v = vec![1, 2, 3, 4, 5];
let sum: i32 = v.iter().sum();
let max = v.iter().max();
let evens: Vec<&i32> = v.iter().filter(|x| **x % 2 == 0).collect();
let squared: Vec<i32> = v.iter().map(|x| x * x).collect();
let total: i32 = v.iter().fold(0, |a, b| a + b);
iter / iter_mut / into_iter
iter() 不変参照を返す
iter_mut() ミュータブル参照を返す
into_iter() 所有権を渡す
遅延評価
v.iter().map(|x| x * 2).filter(|x| x > &5)
// この時点では何も実行されていない(イテレータが構築されただけ)
.collect::<Vec<_>>() // 終端操作で初めて実行
Rustのイテレータは ゼロコスト抽象化。手書きのforループとほぼ同じ機械語が生成される。
9-3. このセクションのまとめ
- Vec / String / HashMapが主要
- iter() / map / filter / collectが標準
- 遅延評価でゼロコスト
- iter / iter_mut / into_iterの使い分け
10. スマートポインタ(Box / Rc / Arc / RefCell)
「所有権ルールでは表現できない場面」のための仕組み。
10-1. Box
ヒープに値を置く。
let b = Box::new(5); // ヒープに5を置く
// 再帰型の表現に必須
enum List {
Cons(i32, Box<List>),
Nil,
}
10-2. Rc(reference counting)
複数の所有者を持つ(シングルスレッド)。
use std::rc::Rc;
let a = Rc::new(String::from("hello"));
let b = Rc::clone(&a); // 参照カウント+1
println!("count = {}", Rc::strong_count(&a)); // 2
Pythonの参照カウントと同じ仕組み。循環参照に注意(Weak<T> で対処)。
10-3. Arc(atomic Rc)
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
並行処理で複数スレッドからアクセスする場合に使う。
10-4. RefCell(内部可変性)
「不変参照しか持っていないが、内側を変更したい」場面。実行時にチェック。
use std::cell::RefCell;
let c = RefCell::new(5);
*c.borrow_mut() += 1;
*c.borrow(); // 6
ルール違反は ランタイムでpanic。コンパイル時チェックを諦める代わりに柔軟性を得る。
10-5. このセクションのまとめ
Box<T>: ヒープ配置、再帰型
Rc<T>: 複数所有(single-thread)
Arc<T>: 複数所有(multi-thread)
RefCell<T>: 内部可変性、ランタイムチェック
Mutex<T>: 並行で内部可変性(次章)
11. 並行処理(Send / Sync / Mutex)
Rustの 「Fearless Concurrency」の中核。データレースをコンパイル時に防ぎます。
11-1. thread
use std::thread;
let h = thread::spawn(|| {
println!("hi from thread");
});
h.join().unwrap();
moveクロージャ
let v = vec![1, 2, 3];
thread::spawn(move || {
println!("{:?}", v);
}).join().unwrap();
move で所有権をthreadに移動。これがないと「ライフタイムがthreadを超える保証がない」とエラー。
11-2. SendとSync
Send: スレッド間で「所有権を移動できる」
Sync: スレッド間で「参照を共有できる」
これらは トレイトで、ほとんどの型は自動で実装される。Rc<T> は Send でも Sync でもないので、複数スレッドから使えない。代わりに Arc<T>。
11-3. Mutex / RwLock
use std::sync::Mutex;
let m = Mutex::new(0);
{
let mut guard = m.lock().unwrap();
*guard += 1;
} // ガードがドロップで自動的にunlock
「所有権を借りているうちはロック解放されない」という型による安全性。
Arc<Mutex> パターン
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let h = thread::spawn(move || {
let mut n = counter.lock().unwrap();
*n += 1;
});
handles.push(h);
}
for h in handles { h.join().unwrap(); }
println!("{}", *counter.lock().unwrap()); // 10
11-4. mpsc channel
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("hello").unwrap();
});
let msg = rx.recv().unwrap();
multi-producer, single-consumer channel。Goのchannelと似たアプローチ。
11-5. このセクションのまとめ
- thread::spawn + move
- Send/Syncで安全性をコンパイル時に保証
- Arc<Mutex<T>> が定番
- mpsc channel
- データレース不可能(コンパイラが拒否)
12. async / await
Rust 1.39で安定化。ゼロコストな非同期実行を提供します。
12-1. async fnとawait
async fn fetch(url: &str) -> Result<String, reqwest::Error> {
let body = reqwest::get(url).await?.text().await?;
Ok(body)
}
#[tokio::main]
async fn main() {
let body = fetch("https://example.com").await.unwrap();
println!("{}", body);
}
async fn は Future を返す関数。await で実行する。
12-2. ランタイム(tokio / async-std)
Rustのasyncは 言語に組み込まれていないので、外部ランタイムが必要。tokio が事実上の標準。
tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() { ... }
12-3. 並行実行
use tokio::join;
let (a, b) = join!(fetch_a(), fetch_b());
tokio::join! で複数のFutureを 並行に実行。
spawn
let handle = tokio::spawn(async {
do_work().await
});
let result = handle.await.unwrap();
12-4. このセクションのまとめ
- async fn + .await
- tokioが事実上の標準ランタイム
- join! / spawnで並行
- ゼロコスト:ステートマシンに変換される
13. マクロ
Rustのマクロは Cのテキスト置換マクロより遥かに強力。3種類あります。
13-1. 宣言的マクロ(macro_rules!)
macro_rules! square {
($x:expr) => { $x * $x };
}
square!(5); // 25
println!、vec!、format! などすべてマクロ。
13-2. deriveマクロ
#[derive(Debug, Clone)]
struct Point { x: i32, y: i32 }
第7章7-4で扱った。
13-3. 手続きマクロ(procedural macro)
#[my_attribute]
fn foo() {}
#[derive(MyTrait)]
struct Foo;
最も強力。コードを別のコードに変換するコンパイラプラグイン。RustのDSL(serde、tokio、actix)の核。
13-4. このセクションのまとめ
- macro_rules! パターンベースの宣言的マクロ
- derive自動実装
- proc macro DSLの核(serde, tokio, sqlx)
- Cより強力で型安全
14. unsafeとFFI
Rustの エスケープハッチ。所有権ルールの外で書ける領域。
14-1. unsafeブロック
unsafe {
let p = ... as *mut i32;
*p = 42;
}
unsafe でできること:
- 生ポインタの参照外し
unsafe fnを呼ぶ- 可変なstaticの読み書き
- FFI(外部関数呼び出し)
unsafe は 最小限にし、安全なAPIでラップするのが定石。
14-2. FFI(Cとの相互運用)
extern "C" {
fn abs(x: i32) -> i32;
}
unsafe {
println!("{}", abs(-5));
}
Cのライブラリを呼べる。Rustの生命線で、既存のCエコシステムと統合できます。
14-3. このセクションのまとめ
- unsafeで所有権ルールを越える
- 生ポインタ、unsafe fn、static mut、FFI
- 最小限にし、安全なAPIでラップ
15. モジュール・クレート・workspace
Rustの コード組織化。
15-1. モジュール
// src/main.rs
mod foo; // src/foo.rsを読む
mod bar { // インラインモジュール
pub fn hello() {}
}
use foo::something;
use bar::hello;
pubとprivate
mod my_mod {
pub fn public_fn() {}
fn private_fn() {} // モジュール外から見えない
}
デフォルトでprivate。
15-2. クレート
「コンパイル単位」。
- バイナリクレート:
src/main.rsから始まる、実行可能 - ライブラリクレート:
src/lib.rsから始まる、他から使われる
15-3. workspace
複数クレートをまとめるプロジェクト構造。
# Cargo.toml(ルート)
[workspace]
members = ["app", "lib", "common"]
大規模プロジェクトで標準的。
15-4. このセクションのまとめ
- modでモジュール、pubで公開
- main.rs / lib.rsがエントリ
- workspaceで多クレート管理
16. テストとドキュメント
16-1. ユニットテスト
fn add(a: i32, b: i32) -> i32 { a + b }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
}
cargo test で実行。
16-2. ドキュメントテスト
/// 2つの数を足す
///
/// # Example
/// ```
/// assert_eq!(mylib::add(1, 2), 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
ドキュメント内のコード例も 実行されてテストされる。
16-3. cargo doc
cargo doc --open
すべての公開APIのHTMLドキュメントを生成。crates.ioで自動公開される。
16-4. このセクションのまとめ
- #[test] + cargo test
- doc testでドキュメントの正確性も保証
- cargo docで立派なドキュメント
17. Rust 2018/2021 Editionと新機能
Rustの エディションは3年ごとに非互換変更を導入する仕組み。古いコードは古いエディションのまま動き続けます。
2015 Rust 1.0のエディション
2018 ?演算子普及、asyncプレビュー、モジュール簡略化
2021 format!のキャプチャ、disjoint capture in closures
2024予定(in flight)
Rust 1.39+ の重要マイルストーン
1.39 (2019) async/await安定化
1.51 (2021) const generics
1.56 (2021) edition 2021
1.65 (2022) GATs、let-else
1.75 (2023) async fn in trait
18. よくある落とし穴FAQ
Q1. ライフタイム 'staticと 'aの違いは?
'static は プログラム全体生きる。'a は任意のライフタイム。
Q2. Stringと &str
String は所有、&str は借用。関数引数は &str を取るのが慣習。
Q3. clone() は遅い?
Vec/Stringのcloneはheap allocationを伴う。Rc::clone は参照カウントのincrementのみで安い。
Q4. ? は何でも使える?
ResultまたはOptionを返す関数内のみ。From トレイトで型変換される。
Q5. dynとimplの違い
fn f(x: impl Trait) { ... } // ジェネリクス(静的)
fn f(x: &dyn Trait) { ... } // 動的ディスパッチ
fn f() -> impl Trait { ... } // 戻り値の型を隠す
Q6. 借用チェッカに勝てない
多くの場合、設計を見直すサイン。Rc<RefCell<T>> で逃げるのは最終手段。
Q7. asyncとsyncを混ぜる
ブロッキング呼び出しは tokio::task::spawn_blocking で逃がす。
Q8. Dropはいつ呼ばれる?
スコープを抜けたとき。明示的に drop(x) も呼べる。
Q9. unsafeは本当に必要か?
ほとんどのアプリで不要。FFIかパフォーマンスクリティカルな箇所のみ。
Q10. cargo buildが遅い
インクリメンタルビルドで2回目以降は速い。cargo check は型チェックのみで一番速い。
19. 図解: 所有権と借用
所有権(move):
let s1 = String::from("hi");
┌─────────┐ ┌────────┐
│ s1 │ ─→ │ "hi" │
└─────────┘ └────────┘
let s2 = s1; // ムーブ
┌─────────┐ ┌────────┐
│ s1 │ X │ "hi" │
└─────────┘ └────────┘
↑
s2が所有
借用(&):
let r = &s2;
┌─────────┐ ┌────────┐
│ s2 │ ─→ │ "hi" │ ← s2が所有
│ r │ ─→ │ │ ← rは借りている
└─────────┘ └────────┘
20. 学習ロードマップ(30日)
Week 1: 基礎
- 型・変数・関数・制御フロー
- 所有権・借用(最大の山場)
Week 2: 構造化
- struct / enum / match
- trait / ジェネリクス
Week 3: 実用
Week 4: 並行・配布
21. 用語集
- 所有権: 値の唯一の所有者という概念
- 借用: 所有権を移さず参照を渡す
- ライフタイム: 参照の有効期間
- trait: 振る舞いの集合
- enum: ADT、各バリアントが値を持てる
- Send/Sync: スレッド安全性のトレイト
- Future: 非同期計算の単位
- crate: コンパイル単位
- edition: 非互換変更を許すRustの3年サイクル
- unsafe: 所有権ルール外のエスケープハッチ
発展: 所有権と型の詳細
ここからはRustの 核心トピックを実例とともに深掘り。所有権・ライフタイム・トレイト・async・unsafeを完全に理解する世界です。
22. 所有権・借用・ライフタイム詳解
Rustの最大の山場。コンパイラとの「対話」が苦痛から喜びに変わるまで、徹底的に解説します。
22-1. 所有権の真の意味
「RAIIを言語の中心に据えた」のがRust。各値には所有者がおり、所有者がスコープを抜けると自動的にリソースが解放されます。
fn main() {
let s = String::from("hello"); // sが所有者
process(s); // 所有権がmove
// ここでsは使えない!
}
fn process(s: String) { // sは新しい所有者
println!("{}", s);
} // ここでsがdropされる
値はどこでdropされるか
struct Loud(i32);
impl Drop for Loud {
fn drop(&mut self) { println!("dropped {}", self.0); }
}
fn main() {
let _a = Loud(1);
{
let _b = Loud(2);
} // dropped 2
let _c = Loud(3);
} // dropped 3, dropped 1(逆順)
「スコープを抜ける順 = dropの順」。宣言の逆順でdropされます。
22-2. MoveとCopyの境界
// Copyトレイトを実装する型
let x = 10; // i32はCopy
let y = x; // xはまだ使える(コピーされる)
// Copyではない型(String、Vecなど)
let s1 = String::from("hi");
let s2 = s1; // move
// println!("{}", s1); // エラー!
// クローン(明示的なコピー)
let s1 = String::from("hi");
let s2 = s1.clone(); // 明示的にディープコピー
println!("{}", s1); // OK
Copyトレイトの条件
Copy を実装できるのは:
- すべてのフィールドがCopy
Dropを実装していない
つまり「自明にビット単位コピーが安全な型」だけがCopy。
#[derive(Copy, Clone)]
struct Point {
x: f64,
y: f64,
}
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // コピー
// p1もまだ使える
22-3. 関数引数の所有権
fn take(s: String) { // 所有権を奪う
println!("{}", s);
} // ここでsがdrop
fn borrow(s: &String) { // 借用
println!("{}", s);
} // sはdropされない(参照だから)
fn borrow_mut(s: &mut String) {
s.push_str(" world");
}
fn main() {
let s = String::from("hi");
borrow(&s); // OK、sはまだ生きている
take(s); // sがmove、もう使えない
}
「多くの場合、関数は値をborrowすべき」が原則。所有権を奪うのは「保存・変換」したいときだけ。
22-4. 借用の規則(再確認)
1. 参照は常に有効な値を指す(ダングリング不可)
2. 同時に有効な借用は:
- 任意個の不変借用(&T) または
- 1つだけのミュータブル借用(&mut T)
- 不変とミュータブルは同時にダメ
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s; // OK、複数の不変借用
println!("{} {}", r1, r2);
// r1, r2はもう使われない(NLL: Non-Lexical Lifetimes)
let r3 = &mut s; // OK、r1/r2が「使われていない」のでミュータブル借用OK
r3.push_str(" world");
NLL(非語彙的ライフタイム)
Rust 2018+ では「最後に使われた地点までがライフタイム」と判定(語彙スコープではない)。これにより上のコードのようなケースが書けるようになりました。
22-5. ライフタイム注釈の必要性
// 関数シグネチャにライフタイムが必要な場合
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
'a は 「xとyは同じライフタイム、戻り値もそれと同じ」と表明。コンパイラがこれを検証します。
ライフタイム省略規則
明示しなくても、以下のルールで自動推論:
規則1: 各引数の参照に異なるライフタイムを割り当てる
規則2: 入力ライフタイムが1つだけなら、それを全戻り値に
規則3: メソッド(&self / &mut self)なら、selfのライフタイムを戻り値に
fn first(s: &str) -> &str { ... } // 規則2: &'a str -> &'a str
impl S {
fn name(&self) -> &str { ... } // 規則3: &'a self -> &'a str
}
fn longest(x: &str, y: &str) -> &str { ... }
// 規則だけでは決まらない(複数の入力)→ 明示必須
22-6. structでのライフタイム
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce(&self, ann: &str) -> &str {
println!("Attention: {}", ann);
self.part
}
}
let novel = String::from("Call me Ishmael...");
let first_sentence = novel.split('.').next().unwrap();
let e = Excerpt { part: first_sentence };
参照を保持するstructには 必ずライフタイムが必要。「このstructは元の &str より長く生きない」と表明。
22-7. 'staticライフタイム
let s: &'static str = "hello"; // 文字列リテラルは 'static
'static は 「プログラム終了まで生きる」。文字列リテラル、Box::leak で作ったオブジェクトなど。
'static 制約
fn spawn<F>(f: F) where F: FnOnce() + Send + 'static {
// ...
}
thread::spawn などのシグネチャで 'static を要求するのは、スレッドが親より長く生きるかもしれないためです。スレッドにキャプチャされる値も、十分に長く生きる必要があります。
22-8. ライフタイム関連の典型エラー
1. ダングリング参照
fn dangle() -> &String {
let s = String::from("hi");
&s // エラー:sは関数を抜けるとdrop
}
// 解決: Stringを返す
fn no_dangle() -> String {
String::from("hi")
}
2. 借用と所有権の競合
let mut v = vec![1, 2, 3];
let r = &v[0]; // 不変借用
v.push(4); // エラー!vはミュータブル借用が必要
println!("{}", r);
3. selfと関連する借用
struct Parser<'a> {
input: &'a str,
pos: usize,
}
impl<'a> Parser<'a> {
fn next(&mut self) -> Option<&'a str> {
// ...
}
}
&'a で「selfの借用ではなく、元のinputのライフタイムを継承」と表明。
22-9. このセクションのまとめ
- 所有権: 各値に1つの所有者
- Copyトレイト: ビット単位コピー安全な型
- 借用: &T不変、&mut Tミュータブル
- 借用ルール: 「複数の不変OR 1つのミュータブル」
- NLLで「使われた最後の地点」までがライフタイム
- ライフタイム注釈はstructと複数入力関数で必要
- 'staticは最強のライフタイム
- 借用エラーは設計を見直すサイン
23. トレイト深掘り
Rustの抽象化機構の中核。Java interface + 型クラス + ジェネリクスを統合した強力な仕組み。
23-1. traitの基本構文
trait Animal {
// 必須メソッド
fn name(&self) -> &str;
// デフォルト実装付きメソッド
fn introduce(&self) -> String {
format!("I am {}", self.name())
}
}
struct Dog { name: String }
impl Animal for Dog {
fn name(&self) -> &str { &self.name }
// introduceはデフォルト実装を使う
}
23-2. trait境界(trait bound)
fn print<T: Display>(x: T) {
println!("{}", x);
}
// where句で書く
fn process<T, U>(t: T, u: U)
where
T: Display + Clone,
U: Debug + PartialOrd,
{
// ...
}
// impl Trait(C++ Conceptsに近い構文糖)
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
23-3. 演算子オーバーロード
use std::ops::Add;
#[derive(Clone, Copy)]
struct Point { x: f64, y: f64 }
impl Add for Point {
type Output = Point;
fn add(self, rhs: Point) -> Point {
Point { x: self.x + rhs.x, y: self.y + rhs.y }
}
}
let p = Point { x: 1.0, y: 2.0 } + Point { x: 3.0, y: 4.0 };
traitで演算子をオーバーロード。Add、Sub、Mul、Div、PartialEq、PartialOrd 等。
23-4. associated type
trait Iterator {
type Item; // 関連型
fn next(&mut self) -> Option<Self::Item>;
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> { ... }
}
「実装側で型を決める」ジェネリクス。Vec<T>::iter() の戻り値型を表現。
23-5. traitオブジェクト(dyn)
fn process(items: Vec<Box<dyn Animal>>) {
for item in &items {
println!("{}", item.introduce());
}
}
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog { name: "Rex".into() }),
Box::new(Cat { name: "Mike".into() }),
];
process(animals);
dyn Trait で 動的ディスパッチ(vtable経由)。同じ型の集合じゃなくても扱える。
object safe trait
すべてのtraitがtraitオブジェクトになれるわけではない。条件:
Sizedを要求しない- 関連型でジェネリクスを使わない
Self: Sizedのメソッドは含む
23-6. impl Traitの用途
// 1. 関数の戻り値(具体的な型を隠す)
fn make_iter() -> impl Iterator<Item = i32> {
(0..10).map(|x| x * 2)
}
// 2. 関数引数(ジェネリクスの簡略化)
fn print_all(items: impl Iterator<Item = String>) { ... }
// 3. async fnの戻り値(C++ の戻り値型推論に近い)
async fn fetch() -> impl Future<Output = String> { ... }
23-7. 高度なトレイト
// supertrait(継承)
trait Display: Debug { ... }
// blanket impl(万人向け実装)
impl<T: Display> ToString for T {
fn to_string(&self) -> String { format!("{}", self) }
}
// 関連定数
trait Bounded {
const MAX: Self;
const MIN: Self;
}
23-8. deriveマクロ完全版
#[derive(
Debug, // {:?} で表示
Clone, // .clone()
Copy, // 暗黙コピー(Cloneも必要)
PartialEq, Eq, // == 比較
Hash, // HashMapキー
Default, // T::default()
PartialOrd, Ord, // < 比較
)]
struct Point { x: i32, y: i32 }
これだけ書けば 多くの場面で必要なトレイトが揃う。
23-9. このセクションのまとめ
- traitは契約 + デフォルト実装
- where句で複雑な制約
- 演算子オーバーロード(Add等)
- 関連型(type Item)
- dyn Traitで動的ディスパッチ
- impl Traitで型を隠す
- supertrait, blanket impl
- deriveで自動実装
24. ジェネリクスとモノモルフィゼーション
24-1. ジェネリック関数
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
largest(&[1, 5, 3, 2]);
largest(&["a", "c", "b"]);
24-2. ジェネリック構造体
struct Pair<T, U> {
first: T,
second: U,
}
impl<T, U> Pair<T, U> {
fn new(first: T, second: U) -> Self {
Self { first, second }
}
}
// 特定型に限定したimpl
impl<T: Display> Pair<T, T> {
fn print(&self) {
println!("{} {}", self.first, self.second);
}
}
24-3. モノモルフィゼーション
Rustのジェネリクスは コンパイル時に型ごとに別の関数を生成します(C++ templateと同じ)。
fn add<T: Add>(a: T, b: T) -> T::Output { a + b }
add(1i32, 2); // → fn add_i32(a: i32, b: i32) -> i32が生成
add(1.5f64, 2.5); // → fn add_f64(a: f64, b: f64) -> f64が生成
これにより:
- ゼロコスト: 実行時のディスパッチコストなし
- コンパイルが遅くなる
- バイナリが膨らむ
「ゼロコスト抽象化」の核心。Javaのジェネリクス(型消去)とは対照的。
24-4. constジェネリクス(Rust 1.51+)
struct ArrayWrapper<T, const N: usize> {
data: [T; N],
}
let a: ArrayWrapper<i32, 10> = ArrayWrapper { data: [0; 10] };
N のような 値もジェネリクスパラメータにできる。固定長配列の抽象化に有効。
24-5. このセクションのまとめ
- <T> でジェネリック関数・構造体
- where句で制約
- モノモルフィゼーションでゼロコスト
- バイナリサイズ ↔ 性能のトレードオフ
- const genericsで値もパラメータ化
25. エラーハンドリング深掘り
25-1. ResultとOptionの真の理解
enum Option<T> { Some(T), None }
enum Result<T, E> { Ok(T), Err(E) }
「nullや例外を型で表現」する。Java/C# のOptional/exceptionより厳密。
25-2. ? 演算子の内部
fn read_file() -> Result<String, io::Error> {
let s = std::fs::read_to_string("file")?;
Ok(s.to_uppercase())
}
? は内部で:
match expr {
Ok(v) => v,
Err(e) => return Err(From::from(e)),
}
From トレイト経由で エラー型を変換する。これにより:
fn handler() -> Result<(), MyError> {
let s = std::fs::read_to_string("...")?; // io::Error → MyError変換
let n: i32 = s.parse()?; // ParseIntError → MyError
Ok(())
}
impl From<io::Error> for MyError { ... }
impl From<ParseIntError> for MyError { ... }
25-3. anyhow(アプリ向け)
use anyhow::{Result, Context, anyhow};
fn run() -> Result<()> {
let cfg = read_config().context("failed to read config")?;
let data = parse(&cfg).context("failed to parse")?;
if data.is_empty() {
return Err(anyhow!("data is empty"));
}
Ok(())
}
雑な例外的扱いでいい場面(CLIツール、Webサーバ)に最適。エラーチェインも自動。
25-4. thiserror(ライブラリ向け)
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("parse error at line {line}: {message}")]
Parse { line: usize, message: String },
#[error("data not found")]
NotFound,
}
#[derive(Error)] で Display、From、Source などを自動生成。ライブラリの公開エラー型に最適。
25-5. パニックハンドリング
panic!("unreachable!");
unreachable!();
todo!();
unimplemented!();
assert!(condition);
debug_assert!(condition); // debugビルドのみ
panic! はスタックを巻き戻して終了。catch_unwind で捕獲可能だが、推奨されない。
let result = std::panic::catch_unwind(|| {
panic!("oops");
});
// resultはErr(...)
25-6. このセクションのまとめ
- Result/Optionで型によるnull/例外表現
- ? はFromでエラー型を自動変換
- anyhow(アプリ)+ thiserror(ライブラリ)が定番
- panicは回復不能なバグ用
- catch_unwindは最終手段
26. async / await完全版
26-1. Future trait
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
pub enum Poll<T> {
Ready(T),
Pending,
}
Future は 「ポーリングして完了を確認する」抽象。コンパイラがasync fnを 状態機械に変換します。
26-2. async fnの変換
async fn example(x: i32) -> i32 {
let a = step1(x).await;
let b = step2(a).await;
b + 1
}
// コンパイラ生成(疑似コード)
enum ExampleState {
Start { x: i32 },
AwaitStep1 { fut: Step1Fut, x: i32 },
AwaitStep2 { fut: Step2Fut, a: i32 },
Done,
}
impl Future for Example {
type Output = i32;
fn poll(...) -> Poll<i32> {
// 各awaitごとに状態遷移
}
}
ゼロコスト: ヒープ割り当てなし、関数呼び出しのオーバーヘッドのみ。
26-3. Tokioの基本
#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
do_work().await;
});
handle.await.unwrap();
}
tokio::spawn で 非同期タスクを起動。スレッドプールで並行実行される。
26-4. select! とjoin!
use tokio::join;
use tokio::select;
// 全部完了を待つ
let (a, b, c) = join!(fetch_a(), fetch_b(), fetch_c());
// 最初に完了したもの
select! {
a = fetch_a() => println!("a: {:?}", a),
b = fetch_b() => println!("b: {:?}", b),
_ = tokio::time::sleep(Duration::from_secs(5)) => {
println!("timeout");
}
}
26-5. asyncブロック
let fut = async {
let x = step().await;
x + 1
};
let result = fut.await;
async { ... } でFutureを作る。クロージャと同じく外側の変数を借用する。async move { ... } で所有権を奪う。
26-6. Stream(非同期イテレータ)
use futures::stream::{self, StreamExt};
let mut stream = stream::iter(vec![1, 2, 3]);
while let Some(x) = stream.next().await {
println!("{}", x);
}
// TokioのchannelからStream
let (tx, mut rx) = tokio::sync::mpsc::channel(10);
while let Some(msg) = rx.recv().await {
process(msg).await;
}
26-7. asyncとSend / Sync
async fn handle() -> Result<()> {
let mutex_guard = Mutex::new(0).lock().unwrap();
do_async().await; // ガードがawaitを跨ぐ → Sendにならない!
Ok(())
}
MutexGuard は Send ではないので、await を跨ぐとasync fnが Send でなくなる → tokio::spawn できない。
対策:
async fn handle() -> Result<()> {
{
let g = Mutex::new(0).lock().unwrap();
// gを使う
} // ここでドロップ
do_async().await;
Ok(())
}
26-8. async fn in trait(Rust 1.75+)
trait Repository {
async fn find(&self, id: i32) -> Option<User>;
}
impl Repository for MyRepo {
async fn find(&self, id: i32) -> Option<User> { ... }
}
長らく trait objectにasync fnを入れにくい問題がありましたが、Rust 1.75で async fn in trait が安定化。async-trait クレートの代替に。
26-9. このセクションのまとめ
- async fnは状態機械にコンパイル(ゼロコスト)
- Tokioが事実上の標準ランタイム
- join! / select! で並行
- Streamで非同期イテレータ
- awaitを跨ぐSend問題に注意
- Rust 1.75+ でasync fn in trait
27. unsafe Rust
「コンパイラに信頼してくれ、と頼む」モード。所有権ルールの外で書ける領域。
27-1. unsafeでできること
unsafe {
// 1. 生ポインタの参照外し
let x = 5;
let p = &x as *const i32;
let val = *p;
// 2. unsafe fnの呼び出し
dangerous_function();
// 3. mutable staticの読み書き
static mut COUNTER: i32 = 0;
COUNTER += 1;
println!("{}", COUNTER);
// 4. unsafe traitの実装
// 5. extern "C" 関数の呼び出し
}
通常のRustコードでは不可能なことを、unsafe ブロック内で許可。
27-2. 生ポインタ
let x = 5;
let r1: *const i32 = &x; // 不変生ポインタ
let r2: *mut i32 = &mut x as *mut i32; // ミュータブル
unsafe {
println!("{}", *r1);
}
Cのポインタ相当。借用チェッカが効かないので、自分で安全性を保証する必要がある。
27-3. FFI(Foreign Function Interface)
extern "C" {
fn abs(x: i32) -> i32;
}
unsafe {
println!("{}", abs(-5));
}
Cライブラリの呼び出し。Rustは CのABIと完全互換で、世界中のCライブラリを呼べる。
// RustからCに公開
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
27-4. 安全なAPIでラップする
pub fn safe_wrapper(x: i32) -> i32 {
unsafe {
// ここでC関数を呼ぶが、APIは安全
c_function(x)
}
}
「unsafeは最小化、外部には安全なインターフェース」がRustの設計指針。標準ライブラリの Vec、Box の内部にも unsafe がある。
27-5. このセクションのまとめ
- unsafeで所有権ルールを越える
- 生ポインタ・unsafe fn・static mut・FFI
- 最小限にして安全APIでラップ
- CのABIと完全互換
28. マクロ深掘り
28-1. macro_rules!
macro_rules! vec2 {
($($x:expr),*) => {
{
let mut v = Vec::new();
$(v.push($x);)*
v
}
};
}
let v = vec2![1, 2, 3, 4];
$x:expr で式を捕獲、$(...)+ で繰り返し。println!、format!、vec! はすべてmacro_rules!。
28-2. 手続きマクロ(procedural macro)
// deriveマクロ
#[proc_macro_derive(MyTrait)]
pub fn my_trait_derive(input: TokenStream) -> TokenStream {
// 入力ASTを解析
let ast: DeriveInput = syn::parse(input).unwrap();
// 出力コード生成
quote! {
impl MyTrait for #ast { ... }
}.into()
}
// 属性マクロ
#[my_attribute]
fn foo() {}
// 関数風マクロ
my_macro!(some_input);
syn(パーサ)+ quote(コード生成)が事実上の標準。serde、tokio、actixの魔法はすべてこれ。
28-3. deriveを使った定型コード削減
#[derive(serde::Serialize, serde::Deserialize)]
struct User {
name: String,
age: u32,
}
let user = User { name: "Alice".into(), age: 30 };
let json = serde_json::to_string(&user)?;
let user2: User = serde_json::from_str(&json)?;
JSON / YAML / TOML / MessagePackなどとの相互変換が derive 1つで完了。
28-4. このセクションのまとめ
- macro_rules! でパターンベース宣言マクロ
- proc macroでコード生成
- deriveで定型実装
- serde / tokio / actixの核心
29. パフォーマンスチューニング
29-1. ゼロコスト抽象化
Rustは「手で書いたCと同じくらい速い」抽象化を目指して設計されています。
// イテレータチェイン
let sum: i32 = (1..=100).filter(|x| x % 2 == 0).sum();
// 同等の手書きループ
let mut sum = 0;
for x in 1..=100 {
if x % 2 == 0 { sum += x; }
}
両者は ほぼ同じアセンブリにコンパイルされます。
29-2. プロファイリング
# perf
cargo build --release
perf record ./target/release/myapp
perf report
# flamegraph
cargo install flamegraph
cargo flamegraph
# criterionでベンチマーク
[dev-dependencies]
criterion = "0.5"
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn benchmark(c: &mut Criterion) {
c.bench_function("my_function", |b| {
b.iter(|| my_function(black_box(input)));
Criterion による継続的ベンチマーク
// benches/my_bench.rs
use criterion::*;
use my_crate::*;
fn bench_vector_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("vector");
for size in [100, 1000, 10000].iter() {
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
let v = (0..size).collect::<Vec<_>>();
b.iter(|| v.iter().filter(|x| x % 2 == 0).count());
});
}
group.finish();
}
criterion_group!(benches, bench_vector_operations);
criterion_main!(benches);
実行: cargo bench で自動的に比較レポート生成
29-3. リリースビルドの最適化
# Cargo.toml
[profile.release]
opt-level = 3 # 最大最適化
lto = true # リンク時最適化
codegen-units = 1 # 単一ユニットコンパイル (遅いが高度な最適化)
strip = true # シンボル削除
Rustのreleaseビルドは、同じC++のrelease buildと同等かそれ以上のパフォーマンスを達成します。
29-4. アロケーション削減
// 悪い例: 毎回新規アロケーション
let mut results = Vec::new();
for batch in data.chunks(100) {
let v = process(batch); // 新しいVecを作成
results.extend(v); // コピー
}
// 良い例: 事前確保
let mut results = Vec::with_capacity(data.len());
let mut temp = Vec::new();
for batch in data.chunks(100) {
temp.clear();
process_into(&mut temp, batch); // 既存バッファを再利用
results.extend_from_slice(&temp);
}
with_capacity、reserve、clear を使うことでアロケーション回数を最小化。
29-5. SIMD とインラインアセンブリ
// nightly, unstable
#![feature(portable_simd)]
use std::simd::*;
fn simd_add(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
let av = SimdFloat::from_array(a);
let bv = SimdFloat::from_array(b);
(av + bv).to_array()
}
// インラインアセンブリ(特殊な最適化が必要な場合)
use std::arch::x86_64::*;
unsafe fn fast_memcpy(dst: *mut u8, src: *const u8, len: usize) {
// SSE/AVX命令直接使用
// (通常はmemmoveで十分)
}
29-6. このセクションのまとめ
- Rustは設計上ゼロコスト抽象化の言語
- release ビルドとltoで追加最適化
- criterion でベンチマーク自動比較
- アロケーション削減がスピード向上の第1歩
- SIMD・inline asmは一部の特殊ケースのみ
30. エコシステムと実践的な組み合わせ
30-1. Webアプリケーション
非同期ランタイム: tokio (事実上の標準、actix / axum / rocket が構築される)
// Cargo.toml
[dependencies]
tokio = { version = "1.35", features = ["full"] }
axum = "0.7"
serde = { version = "1.0", features = ["derive"] }
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello" }))
.route("/users/:id", get(get_user));
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
30-2. CLIツール
標準ライブラリ + clap で十分、Rustらしい完結したツール
use clap::Parser;
#[derive(Parser)]
#[command(name = "myapp")]
#[command(about = "Does something", long_about = None)]
struct Args {
/// Input file
#[arg(short, long)]
input: String,
/// Output file
#[arg(short, long)]
output: Option<String>,
/// Verbose output
#[arg(short, long)]
verbose: bool,
}
fn main() {
let args = Args::parse();
println!("Input: {}", args.input);
}
ビルド成果物: --release で単一の実行可能ファイル、静的リンク、ゼロ依存で配布可能
30-3. システムプログラミング (OS / 組み込み)
No_std 環境での開発
#![no_std]
extern crate alloc;
use alloc::string::String;
use core::fmt::Write;
fn main() {
let mut s = String::new();
writeln!(&mut s, "Hello").unwrap();
}
Bare-metal Rust
#![no_std]
#![no_main]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn main() {
// boot code
}
Rustは Linuxカーネルモジュール、WindowsドライバーOSレベルのコードに採用されています (RFC 3346 参照)
30-4. 並行プログラミング
Tokio + structured concurrency
use tokio::task::JoinSet;
#[tokio::main]
async fn main() {
let mut set = JoinSet::new();
for i in 0..10 {
set.spawn(async move {
expensive_operation(i).await
});
}
while let Some(res) = set.join_next().await {
println!("{:?}", res);
}
}
データレースは コンパイル時に完全排除。送信元と受信元が型で保証される。
30-5. 依存関係管理のベストプラクティス
Cargo.lock と最小権限
# Cargo.toml
[dependencies]
serde = "1.0" # 1.0.x の最新 (1.1は含まない)
tokio = "1" # 1.x の最新
regex = { version = "1", features = ["unicode"] }
[dev-dependencies]
criterion = "0.5"
[profile.release]
opt-level = 3
lto = true
Cargo.lockをコミット: バイナリクレートで再現性重視Cargo.lockをコミットしない: ライブラリクレート、下流に委ねるcargo auditで脆弱性検査 (CVE-2021-xxxxx の自動検出)cargo denyでライセンス・依存関係ポリシー
30-6. パッケージング と配布
# crates.io へ公開
cargo publish
# 自前レジストリ(Artifactory / Nexus )
cargo install --index https://...
# Git 依存関係
[dependencies]
mylib = { git = "https://github.com/user/repo.git", rev = "abc123" }
30-7. このセクションのまとめ
- Web: tokio + axum / rocket
- CLI: clap + std だけで完結
- OS: no_std / embedded Rust
- Concurrency: async/await + tokio
- 依存: cargo.lock の理解
- 配布: cargo publish / 自前レジストリ
29-3. メモリプロファイル
# Valgrind(Linux)
valgrind --tool=massif ./target/release/myapp
# heaptrack
heaptrack ./target/release/myapp
heaptrack_gui heaptrack.myapp.*.zst
29-4. 最適化フラグ
# Cargo.toml
[profile.release]
opt-level = 3
lto = true # Link-Time Optimization
codegen-units = 1 # より集約的最適化(コンパイル遅い)
panic = "abort" # panicで即終了(バイナリサイズ削減)
strip = true # シンボル削除
29-5. SIMD
#![feature(portable_simd)] // nightly
use std::simd::{f32x4, SimdFloat};
let a = f32x4::from_array([1.0, 2.0, 3.0, 4.0]);
let b = f32x4::from_array([5.0, 6.0, 7.0, 8.0]);
let c = a + b;
stable Rustでは std::arch のintrinsicsまたは wide、packed_simd クレート。
29-6. このセクションのまとめ
- ゼロコスト抽象化が原則
- criterionでベンチマーク
- perf / flamegraphでプロファイル
- LTO / panic=abortで最適化ビルド
- SIMDはnightlyかstd::arch
30. エコシステム主要クレート
- tokio: 非同期ランタイム
- async-std: tokioの代替
- serde: シリアライゼーション
- reqwest: HTTPクライアント
- axum / actix-web / rocket: Webフレームワーク
- sqlx / diesel / sea-orm: DB
- clap: CLI引数パース
- anyhow / thiserror: エラー
- tracing: 構造化ロギング
- rayon: データ並列
- crossbeam: 並行プリミティブ
- parking_lot: 高速Mutex
- once_cell / lazy_static: 遅延初期化
- regex: 正規表現
- chrono / time: 日時
- uuid: UUID
- bytes: 効率的なバッファ
- bevy: ゲームエンジン
- wgpu: GPU
- pyo3: Pythonバインディング
- napi-rs: Node.jsバインディング
これらは crates.io で公開されています。
31. Rust拡張FAQ
Q1. cloneを多用してもいい?
性能クリティカルでなければOK。Vec/Stringのcloneはヒープ確保があるが、それで設計が単純になるなら問題ない。所有権の壁を越えるための手段。
Q2. Rc<RefCell> を使ってもいい?
「所有権ルールでは表現できない」場合に有効。ただし初手では避ける。設計を見直してから使う。
Q3. unsafeはいつ使う?
FFI、ハードウェアアクセス、性能クリティカルな低レベル操作のみ。普通のアプリでは不要。
Q4. asyncとsyncの混在
ブロッキング呼び出しは tokio::task::spawn_blocking で別スレッドに逃がす。
Q5. lifetime annotationエラー
ほとんどの場合、所有権を持たせるか クローンするのが解決策。安易に 'static を付けない。
Q6. trait objectとgeneric、どっち?
trait object(dyn Trait):
- ヒープに置く(Box<dyn>)か参照(&dyn)
- 動的ディスパッチ(vtable)
- 型を統一できない場面に
generic(impl Trait, T: Trait):
- モノモルフィゼーション(型ごとに別関数)
- ゼロコスト
- ジェネリック関数
Q7. Box とanyhow::Error
Box<dyn Error> は標準。anyhow::Error はbacktraceやコンテキスト付きで使いやすい。
Q8. impl Traitの制約
戻り値の impl Trait は「1つの具体的な型」を返す。条件で違う型を返すには Box<dyn Trait>。
Q9. derive Defaultが使えない構造体
すべてのフィールドがDefaultを実装している必要がある。手動で impl Default で対応。
Q10. Vecの容量
let mut v = Vec::with_capacity(100); // 容量を指定
v.push(1); // 再割り当てなし
v.shrink_to_fit(); // 余分な容量を返却
Q11. Stringと &strの境界
「入力は &str、保存はString」が原則。String::from(s) または s.to_string()。
Q12. ? の戻り値型
? は Result<T, E> または Option<T> を返す関数内でのみ使える。
Q13. let-else(Rust 1.65+)
let Some(x) = maybe_value else {
return; // Noneの早期return
};
ガード句を綺麗に書ける。
Q14. trait objectのサイズ
let x: dyn Trait; // エラー(サイズが不明)
let x: &dyn Trait; // OK(参照は固定サイズ)
let x: Box<dyn Trait>; // OK
Q15. lifetimeとgenericの関係
fn foo<'a, T>(x: &'a T) -> &'a T { x }
ライフタイムも型パラメータの一種として扱われる。
Q16. 所有権を諦めるtyped-arena
長寿命のオブジェクトを多く作る場合、typed-arena クレートで「arena allocation」を使うと所有権の悩みが減る。
Q17. enumのサイズ最適化
enum E { A(i32), B(i32, i32) }
// サイズ = max + tag
Option<&T> は null pointer optimization で &T と同じサイズ。
Q18. cargo check / cargo clippy
cargo check は型チェックのみで一番速い。cargo clippy はlint。CIで必ず実行。
Q19. macro_rules! のスコープ
#[macro_export] でクレート外に公開。
Q20. cargo testの並列実行
デフォルトで並列。直列にしたいなら --test-threads=1。
Q21. 標準ライブラリのBTreeMap vs HashMap
BTreeMap: ソート順、O(log n)
HashMap: ハッシュ、O(1) 平均
順序が要らないならHashMap。
Q22. iter() / into_iter() / iter_mut() の違い
v.iter() // &Tを返す
v.into_iter() // Tを返す(所有権を奪う)
v.iter_mut() // &mut Tを返す
Q23. Default vs new
慣習として、シンプルな初期化は Default::default()、引数を取るなら new(...)。
Q24. ?Sized
fn foo<T: ?Sized>(x: &T) { ... }
「サイズ不定でもOK」と表明。str や dyn Trait を受け取れる。
Q25. Pinと !Unpin
自参照構造体(async fnの状態機械など)を動かさないためのマーカー。普通のコードでは意識しない。
Q26. PhantomData
「型パラメータを使っていないが、型として保持したい」場合に使うゼロサイズ型。
Q27. cargo workspace
複数クレートを1つにまとめるプロジェクト構成。大規模プロジェクトの標準。
Q28. Rustの競合避ける技法
Cell / RefCell / Mutex で 内部可変性。Atomic* でロックフリー。
Q29. WASMターゲット
cargo build --target wasm32-unknown-unknown
wasm-pack build
ブラウザ・Node.jsで動くRust。
Q30. Embedded Rust
no_std で組み込み。heapless クレートで動的メモリなし。
32. Rust拡張ロードマップ(60日)
Phase 1: 基礎(Day 1-15)
- 環境(rustup、cargo)
- 型・変数・制御フロー
- 所有権・借用(最大の山場)
- struct / enum / match
- collections(Vec / String / HashMap)
Phase 2: 中級(Day 16-30)
- trait / impl
- generic / lifetime
- error handling(Result / ?)
- iterator
- closure
Phase 3: 実用(Day 31-45)
Phase 4: 上級(Day 46-60)
33. Rust拡張用語集
あ行
- アトミック: ロックフリーな並行操作
- アロケータ: メモリ確保戦略(global allocator)
- イテレータ: next() で要素を返すtrait
- インライン化: 関数呼び出しの展開
- オプション: Option
、nullの代替 - オブジェクト安全: trait objectとして使える条件
か行
- 可変借用: &mut T、書き込み可能
- ガベージコレクション: 動的なメモリ管理(Rustにはない!)
- キャスト: asキーワードで型変換
- クロージャ: 環境を捕獲する関数
- コピー: Copy trait、ビット単位コピー
さ行
- 借用: &Tで参照を渡す
- 借用チェッカ: Borrow Checker、コンパイル時の所有権検証
- シャドーイング: letによる同名再宣言
- 所有権: 値の唯一の所有者
- ストリーム: Stream trait、asyncイテレータ
- 静的型付け: コンパイル時に型決定
た行
- トレイト: 振る舞いの集合
- トレイトオブジェクト: dyn Trait、動的ディスパッチ
- 動的ディスパッチ: vtable経由の呼び出し
- 特殊化: 同じジェネリックの特定型に対する別実装
な行
は行
- パターンマッチング: matchによる分解
- borrow checker: 借用検証
- 不変借用: &T、読み取り専用
- ベクター: Vec
、可変長配列
ま行
- マクロ: macro_rules! とproc macro
- ムーブ: 所有権の移動
- モノモルフィゼーション: コンパイル時のジェネリクス展開
や〜わ行
- 遅延評価: イテレータの基本
- ライフタイム: 参照の有効期間
A〜Z
- AOT: Ahead-Of-Time(RustはAOTのみ)
- ABI: Application Binary Interface
- Box
: ヒープに値を置くスマートポインタ - cargo: パッケージマネージャ + ビルドツール
- crate: コンパイル単位
- edition: 互換性のサイクル(2015/2018/2021/2024)
- FFI: Foreign Function Interface
- Future: 非同期計算
- HashMap: ハッシュテーブル
- MIR: Mid-level IR(コンパイラ内部)
- NLL: Non-Lexical Lifetimes
- Pin: 自参照型を固定するマーカー
- Rc / Arc: 参照カウント / アトミック参照カウント
- RefCell: 内部可変性
- rustc: Rustコンパイラ
- rustup: ツールチェイン管理
- Send / Sync: スレッド安全性のマーカートレイト
- Tokio: 主要asyncランタイム
- trait object: dyn Trait
- unsafe: 所有権ルール外
- WASM: WebAssembly
実践: 内部構造とCI
35. スマートポインタの内部
35-1. Box
let b = Box::new(5);
*b; // 5
Box<T> は 「ヒープに置いたTのポインタ」。サイズはポインタ1つ分(8 byte)。Drop で自動解放。
用途
// 1. 再帰型
enum List {
Cons(i32, Box<List>),
Nil,
}
// 2. 大きい値型をスタックに置きたくない
let big: Box<[u8; 1_000_000]> = Box::new([0; 1_000_000]);
// 3. trait object
let shape: Box<dyn Shape> = Box::new(Circle { ... });
35-2. Rc(reference counting)
use std::rc::Rc;
let a = Rc::new(String::from("hi"));
println!("count = {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a);
println!("count = {}", Rc::strong_count(&a)); // 2
drop(b);
println!("count = {}", Rc::strong_count(&a)); // 1
複数の所有者を許すスマートポインタ。シングルスレッド前提。
35-3. Weak(循環参照対策)
use std::rc::{Rc, Weak};
struct Node {
value: i32,
parent: Weak<Node>,
children: RefCell<Vec<Rc<Node>>>,
}
「親への参照は弱参照」とすることで循環を防止。Weak::upgrade() で Option<Rc<T>> を取得。
35-4. RefCell(内部可変性)
use std::cell::RefCell;
let c = RefCell::new(5);
{
let mut m = c.borrow_mut();
*m += 1;
} // mut借用がここで終わる
let r = c.borrow();
println!("{}", *r); // 6
実行時に借用ルールをチェック。違反するとpanic。所有権ルールを「ランタイムに延期」する仕組み。
35-5. Arc + Mutex(並行版)
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let h = thread::spawn(move || {
let mut n = counter.lock().unwrap();
*n += 1;
});
handles.push(h);
}
for h in handles { h.join().unwrap(); }
println!("{}", *counter.lock().unwrap()); // 10
並行処理での標準パターン。Arc<Mutex<T>> がほぼ毎回登場します。
35-6. parking_lotのスマートポインタ
use parking_lot::Mutex;
let m = Mutex::new(0);
*m.lock() += 1; // poisonがない、unwrap不要
標準の std::sync::Mutex は poison(panicでロックされた状態)の概念があり、.lock().unwrap() を書く必要があります。parking_lot クレートはpoisonなしでAPIがシンプル。性能も上。
35-7. このセクションのまとめ
- Box<T>: ヒープ単一所有
- Rc<T>: 単一スレッド共有所有
- Arc<T>: マルチスレッド共有所有
- Weak<T>: 弱参照、循環対策
- Cell<T>: Copy型の内部可変性
- RefCell<T>: 任意型の内部可変性、ランタイムチェック
- Mutex<T>: 並行 + 内部可変性
- parking_lot::MutexでAPI改善
36. クロージャの内部
36-1. Fn / FnMut / FnOnce
trait Fn<Args>: FnMut<Args> { ... }
trait FnMut<Args>: FnOnce<Args> { ... }
trait FnOnce<Args> { ... }
Fn: 不変借用、何度でも呼べる
FnMut: ミュータブル借用、何度でも呼べる
FnOnce: 値を消費(一度しか呼べない)
クロージャはコンパイラが 匿名struct + impl に変換し、適切なtraitを実装します。
let v = vec![1, 2, 3];
let f = move || println!("{:?}", v); // FnOnce(vを消費する場合)
// Fn(読むだけなら)
f();
36-2. クロージャの3形態
// 1. 借用(&T)
let s = String::from("hi");
let print = || println!("{}", s);
print();
print(); // 何度でもOK
// 2. ミュータブル借用(&mut T)
let mut s = String::from("hi");
let mut append = || s.push_str(" world");
append();
append();
println!("{}", s);
// 3. 所有権を奪う(move)
let s = String::from("hi");
let consume = move || println!("{}", s);
consume();
// sはもう使えない
move キーワードで強制的に所有権を奪う。スレッド起動・asyncの境界で必須。
36-3. 関数として保持
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
f(x)
}
let result = apply(|x| x * 2, 5); // 10
// trait objectとして保持
let f: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
ジェネリクスはゼロコスト(インライン化)、Box
36-4. このセクションのまとめ
- Fn / FnMut / FnOnceのヒエラルキー
- クロージャは匿名struct
- moveで所有権を奪う
- ジェネリクスvs Box<dyn> の使い分け
37. イテレータ深掘り
37-1. Iterator trait
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// ... 75を超えるデフォルトメソッド
}
next() を1つ実装すれば、map、filter、fold、sum、collect など 75+のメソッドが無料で手に入る。
37-2. 主要メソッド
let v = vec![1, 2, 3, 4, 5];
// 変形
v.iter().map(|x| x * 2);
v.iter().filter(|&&x| x > 2);
v.iter().filter_map(|&x| if x > 0 { Some(x * 2) } else { None });
v.iter().flat_map(|&x| vec![x, x]);
// 集約
v.iter().sum::<i32>();
v.iter().product::<i32>();
v.iter().fold(0, |acc, &x| acc + x);
v.iter().reduce(|a, b| a.max(b));
v.iter().count();
v.iter().min();
v.iter().max();
// 検査
v.iter().any(|&x| x > 3);
v.iter().all(|&x| x > 0);
v.iter().position(|&x| x == 3);
v.iter().find(|&&x| x > 3);
// 切り出し
v.iter().take(3);
v.iter().skip(2);
v.iter().take_while(|&&x| x < 4);
v.iter().skip_while(|&&x| x < 3);
// チェイン
v.iter().chain(other.iter());
v.iter().zip(other.iter());
v.iter().enumerate();
v.iter().rev();
v.iter().step_by(2);
// 集約
v.iter().collect::<Vec<_>>();
v.iter().collect::<HashMap<_, _>>();
v.iter().collect::<HashSet<_>>();
v.iter().collect::<String>();
37-3. 遅延評価とゼロコスト
let sum: i32 = (1..=1_000_000)
.filter(|x| x % 3 == 0)
.map(|x| x * x)
.sum();
中間Vecを作らず、要素ごとに処理。コンパイラが最適化して、手書きループとほぼ同じアセンブリになります。
37-4. 自作イテレータ
struct Counter { count: u32 }
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
self.count += 1;
if self.count < 6 { Some(self.count) } else { None }
}
}
let sum: u32 = Counter { count: 0 }.zip(Counter { count: 1 })
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
next() を実装すれば全機能が手に入る。Rustの最強のAPI設計の一つ。
37-5. このセクションのまとめ
- next() だけ実装すれば75+ のメソッドが使える
- 遅延評価 + ゼロコスト
- map / filter / fold / collectが定番
- iter / iter_mut / into_iter
- 自作イテレータも簡単
38. 並行処理深掘り
38-1. thread
use std::thread;
let h = thread::spawn(|| {
println!("hi");
});
h.join().unwrap();
moveクロージャ
let v = vec![1, 2, 3];
thread::spawn(move || {
println!("{:?}", v);
}).join().unwrap();
スレッドの寿命が 元のスコープを超えるかもしれないので、所有権を渡す必要がある。
38-2. mpsc channel
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
for i in 0..5 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(i).unwrap();
});
}
drop(tx); // 全senderをdropするとchannelが閉じる
while let Ok(msg) = rx.recv() {
println!("{}", msg);
}
multi-producer single-consumer。Goのchannelに近い。
38-3. crossbeam channel(推奨)
use crossbeam::channel;
let (tx, rx) = channel::unbounded();
// またはbounded(100)
tx.send(42).unwrap();
let msg = rx.recv().unwrap();
// select
crossbeam::select! {
recv(rx1) -> msg => ...,
recv(rx2) -> msg => ...,
default(Duration::from_secs(5)) => println!("timeout"),
}
標準のmpscより機能豊富。select!、bounded、tick channelなど。
38-4. rayon(データ並列)
use rayon::prelude::*;
let v: Vec<i32> = (1..=1_000_000).collect();
let sum: i32 = v.par_iter().sum(); // 並列化
v.par_iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.sum::<i64>();
par_iter() で CPUコアを使い切る並列処理。Java parallelStream / OpenMPに相当。
38-5. atomic操作
use std::sync::atomic::{AtomicUsize, Ordering};
let counter = AtomicUsize::new(0);
counter.fetch_add(1, Ordering::SeqCst);
let value = counter.load(Ordering::SeqCst);
counter.store(100, Ordering::SeqCst);
// CAS
let result = counter.compare_exchange(
expected, new_value,
Ordering::SeqCst, Ordering::SeqCst
);
メモリオーダ:
Relaxed: 順序保証なし、最速
Acquire: load系で順序保証
Release: store系で順序保証
AcqRel: 両方
SeqCst: 順次一貫性、デフォルト級
「まずSeqCst、ボトルネックなら緩める」が安全。
38-6. SendとSync
unsafe trait Send: ?Sized {} // 別スレッドに所有権を移せる
unsafe trait Sync: ?Sized {} // 複数スレッドから参照可能
ほとんどの型は コンパイラが自動的に実装。例外:
Rc<T>: Send/Syncいずれもなし(参照カウントがアトミックでない)
Arc<T>: Send/Sync両方(アトミック参照カウント)
RefCell<T>: Syncではない(ランタイムチェックがスレッド安全でない)
Mutex<T>: Sync(中身がSendなら)
*const T / *mut T: 自動では実装しない(unsafeで)
コンパイラが自動的にスレッド安全性を検証してくれます。
38-7. このセクションのまとめ
- thread::spawn + moveクロージャ
- mpsc / crossbeam channel
- rayonでデータ並列
- AtomicXxxでロックフリー
- Send / Syncで安全性をコンパイル時に
39. asyncランタイム比較
39-1. Tokio(事実上の標準)
tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() {
tokio::join!(task1(), task2());
}
最大のエコシステム。axum、reqwest、tonic、sqlxなどがTokio前提。
39-2. async-std
#[async_std::main]
async fn main() { ... }
std に近いAPI。Tokioとの互換性があり、近年は人気が下がっている。
39-3. smol
軽量ランタイム。組み込みやコマンドラインツール向け。
39-4. monoio(Linux io_uring)
io_uringを使った最先端の高速ランタイム。
39-5. このセクションのまとめ
- Tokioが圧倒的標準
- async-stdはTokio互換
- smolは軽量
- monoioはio_uring(最先端)
40. テストとCI
40-1. ユニットテスト
fn add(a: i32, b: i32) -> i32 { a + b }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_negative() {
assert!(-1 < 0);
}
#[test]
#[should_panic(expected = "divide by zero")]
fn test_panic() {
divide(1, 0);
}
}
cargo test
cargo test test_add # 名前マッチ
cargo test -- --nocapture # println! を表示
cargo test -- --test-threads=1 # 直列実行
40-2. 統合テスト
// tests/integration_test.rs
use my_crate::*;
#[test]
fn test_workflow() {
let result = workflow();
assert_eq!(result, expected);
}
tests/ 配下のファイルは 別クレートとして扱われ、公開APIのみテスト可能。
40-3. ドキュメントテスト
/// Adds two numbers
///
/// # Example
/// ```
/// assert_eq!(my_crate::add(1, 2), 3);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
cargo test でドキュメント中のコードもテストされる。ドキュメントが嘘をつかない保証。
40-4. ベンチマーク
// nightlyでしかstable benchmarkがないため、criterionを使う
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n { 0 => 1, 1 => 1, n => fibonacci(n-1) + fibonacci(n-2) }
}
fn bench(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, bench);
criterion_main!(benches);
cargo bench
統計込みで信頼性の高い計測。
40-5. CI(GitHub Actions)
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- run: cargo test --all-features
- run: cargo clippy -- -D warnings
- run: cargo fmt --check
cargo clippy と cargo fmt --check を必ず実行。
40-6. このセクションのまとめ
- #[test] + cargo test
- 統合テストはtests/
- ドキュメントテストでドキュメントの正確性
- criterionでベンチマーク
- CIでtest / clippy / fmt
41. crates.ioへの公開
41-1. Cargo.tomlの必須項目
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "A brief description"
license = "MIT OR Apache-2.0"
repository = "https://github.com/you/my-crate"
documentation = "https://docs.rs/my-crate"
readme = "README.md"
keywords = ["foo", "bar"]
categories = ["development-tools"]
[lib]
name = "my_crate"
path = "src/lib.rs"
41-2. 公開コマンド
cargo login <token> # 一度だけ
cargo publish --dry-run # 確認
cargo publish # 公開
公開すると rev取り消し不可(version yankはできるが)。慎重に。
41-3. semver
0.1.0 → 0.1.1パッチ(バグ修正)
0.1.0 → 0.2.0マイナー(後方互換性のある追加)
0.1.0 → 1.0.0メジャー(破壊的変更)
0.x.yは実験的扱い、xが上がれば破壊的変更可能
41-4. ドキュメント生成
cargo doc --open
公開時に docs.rs で自動生成・公開される。
42. unsafe Rustの正しい使い方
42-1. 基本ルール
1. unsafeブロックは最小限
2. 安全なAPIでラップ
3. // SAFETY: コメントで安全性を説明
4. miriでUBを検出
42-2. unsafeコードの典型例
// Vecの例(標準ライブラリ風)
struct MyVec<T> {
ptr: *mut T,
len: usize,
cap: usize,
}
impl<T> MyVec<T> {
fn push(&mut self, value: T) {
if self.len == self.cap {
self.grow();
}
// SAFETY: cap分の領域は確保済み、len < cap
unsafe {
std::ptr::write(self.ptr.add(self.len), value);
}
self.len += 1;
}
}
42-3. miriによるUB検出
rustup +nightly component add miri
cargo +nightly miri test
unsafeコードの未定義動作を 動的に検出する強力なツール。
42-4. このセクションのまとめ
- unsafeは最小限
- 安全なAPIでラップ
- SAFETY: コメント必須
- miriでUBチェック
43. 実用パターン集
43-1. Builderパターン
#[derive(Default)]
struct RequestBuilder {
url: Option<String>,
method: Option<String>,
headers: Vec<(String, String)>,
}
impl RequestBuilder {
fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
fn method(mut self, m: impl Into<String>) -> Self {
self.method = Some(m.into());
self
}
fn header(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
self.headers.push((k.into(), v.into()));
self
}
fn build(self) -> Request {
Request {
url: self.url.expect("url required"),
method: self.method.unwrap_or_else(|| "GET".to_string()),
headers: self.headers,
}
}
}
let req = RequestBuilder::default()
.url("https://example.com")
.method("POST")
.header("Content-Type", "application/json")
.build();
mut self を引数で受けて Self を返すパターン。
43-2. Newtypeパターン
struct UserId(u64);
struct OrderId(u64);
fn process(uid: UserId) { ... }
process(UserId(42)); // OK
process(OrderId(42)); // コンパイルエラー!
ゼロコストで 意味的型安全を実現。
43-3. RAIIパターン
struct Lock<'a, T> {
guard: MutexGuard<'a, T>,
}
impl<'a, T> Lock<'a, T> {
fn new(m: &'a Mutex<T>) -> Self {
Self { guard: m.lock().unwrap() }
}
}
// Dropは自動的にMutexGuardが解放
43-4. Type Stateパターン
struct Connection<S> {
socket: Socket,
_state: PhantomData<S>,
}
struct Disconnected;
struct Connected;
struct Authenticated;
impl Connection<Disconnected> {
fn connect(self) -> Connection<Connected> { ... }
}
impl Connection<Connected> {
fn authenticate(self) -> Connection<Authenticated> { ... }
fn send(&self, _: &[u8]) { ... } // Connectedでも送れる?
}
impl Connection<Authenticated> {
fn send(&self, _: &[u8]) { ... }
}
「APIの使い順序を型で強制」する。誤った順序で呼べないようにコンパイル時に検証。
43-5. このセクションのまとめ
- Builderで流暢な構築
- Newtypeで意味的型安全
- RAIIで確実な解放
- Type StateでAPI順序強制
44. Rust学習リソース
書籍
- 『The Rust Programming Language』(公式、無料)
- 『Programming Rust』Jim Blandy
- 『Rust for Rustaceans』Jon Gjengset
- 『Async Rust』
- 『Zero to Production in Rust』Luca Palmieri
Web
- doc.rust-lang.org/book(公式The Book)
- doc.rust-lang.org/rust-by-example
- doc.rust-lang.org/nomicon(unsafe Rustの聖書)
- this-week-in-rust.org
- rustlings(演習)
コミュニティ
- /r/rust(Reddit、最も活発)
- Rust Discord
- rust-users.com(フォーラム)
- crates.io
- lib.rs(curated crate list)
応用: Webと組み込み
46. Webフレームワーク比較
46-1. axum(推奨、Tokioチーム)
use axum::{routing::get, Router, extract::Path, Json};
use serde_json::json;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello, World" }))
.route("/users/:id", get(get_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn get_user(Path(id): Path<u64>) -> Json<serde_json::Value> {
Json(json!({ "id": id, "name": "Alice" }))
}
型安全なextractor、towerエコシステム統合、Tokioチーム公式。現代の事実上の標準。
46-2. actix-web
use actix_web::{web, App, HttpServer, Responder};
async fn hello() -> impl Responder { "Hello, World" }
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
俊速ベンチマークで有名。アクターモデルベース。
46-3. Rocket
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str { "Hello, World" }
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
}
マクロベースの直感的API。長らくnightly requiredだったが、stable化。
46-4. このセクションのまとめ
- axum: Tokio公式、現代の標準
- actix-web: 高速、アクター
- Rocket: 直感的、マクロ多用
- すべてTokioエコシステム
47. データベース連携
47-1. sqlx(コンパイル時SQL検証)
use sqlx::postgres::PgPoolOptions;
let pool = PgPoolOptions::new()
.max_connections(5)
.connect("postgres://...").await?;
#[derive(sqlx::FromRow)]
struct User {
id: i32,
name: String,
email: String,
}
let users: Vec<User> = sqlx::query_as!(
User,
"SELECT id, name, email FROM users WHERE age >= $1",
18
)
.fetch_all(&pool)
.await?;
コンパイル時にSQLを実行可能性検証。型とDBスキーマがチェックされる。revolutionary。
47-2. diesel(type-safe ORM)
use diesel::prelude::*;
let users = users::table
.filter(users::age.ge(18))
.order(users::name)
.load::<User>(&mut conn)?;
クエリビルダ + ORM。マクロでスキーマを生成。
47-3. sea-orm(async ORM)
let users = User::find()
.filter(user::Column::Age.gte(18))
.order_by_asc(user::Column::Name)
.all(&db)
.await?;
ActiveRecord風API。
47-4. このセクションのまとめ
- sqlx: コンパイル時SQL検証
- diesel: type-safe ORM、同期
- sea-orm: async ORM
48. WebAssemblyとRust
48-1. wasm-bindgen
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
wasm-pack build --target web
ブラウザで動くRust。WebAssembly + JavaScriptバインディングが自動生成。
48-2. Yew / Leptos / Dioxus(Rust UIフレームワーク)
use leptos::*;
#[component]
fn App() -> impl IntoView {
let (count, set_count) = create_signal(0);
view! {
<button on:click=move |_| set_count.update(|c| *c += 1)>
"Click me: " {count}
</button>
}
}
fn main() {
leptos::mount_to_body(App);
}
「Rustでフロントエンド」が現実的に。Svelte風のシグナルベース。
48-3. このセクションのまとめ
- wasm-bindgenでブラウザに公開
- Yew / Leptos / DioxusでUI
- WASIでサーバサイドWASM
49. 組み込みRust
49-1. no_std
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_: &PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
no_std で 標準ライブラリなし。マイコン・カーネル・ベアメタル向け。
49-2. heapless
use heapless::Vec;
let mut v: Vec<i32, 16> = Vec::new(); // 容量16のスタックベクター
v.push(1).unwrap();
動的メモリなしのコレクション。組み込み必須。
49-3. embedded-hal
use embedded_hal::digital::v2::OutputPin;
fn blink<P: OutputPin>(pin: &mut P) {
pin.set_high().ok();
delay();
pin.set_low().ok();
}
組み込みの ハードウェア抽象化。複数のマイコンファミリで動くドライバが書ける。
49-4. このセクションのまとめ
- no_stdで標準ライブラリなし
- heaplessで動的メモリなし
- embedded-halで抽象化
- Rustは組み込みでも実用段階
50. Rustと他言語のバインディング
50-1. Python(PyO3)
use pyo3::prelude::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
Pythonから import my_module で呼べる。Pythonの重い処理をRustで書き直す用途。pandasのPolars代替やRuff(リンター)が代表例。
50-2. Node.js(napi-rs)
use napi_derive::napi;
#[napi]
fn sum(a: i32, b: i32) -> i32 { a + b }
Node.jsからrequireできる。
50-3. Ruby(magnus)
use magnus::{define_module, function};
#[magnus::init]
fn init() -> Result<(), magnus::Error> {
let module = define_module("MyRust")?;
module.define_singleton_method("hello", function!(hello, 1))?;
Ok(())
}
fn hello(name: String) -> String {
format!("Hello, {}!", name)
}
50-4. このセクションのまとめ
- PyO3: Python(最も成熟)
- napi-rs: Node.js
- magnus: Ruby
- jni-rs: Java
- 各言語の重い処理をRustに逃がせる
51. Rustツールチェイン詳細
51-1. rustupの管理
rustup show # 現在の設定
rustup update # 全バージョン更新
rustup default stable # デフォルトをstableに
rustup install nightly
rustup install 1.70.0 # 特定バージョン
rustup component add clippy rust-analyzer rustfmt
rustup target add wasm32-unknown-unknown
rustup target add x86_64-unknown-linux-musl
51-2. cargoの便利機能
cargo new my_app # アプリ
cargo new --lib my_lib # ライブラリ
cargo init # 既存ディレクトリで
cargo build
cargo build --release
cargo run -- arg1 arg2
cargo test
cargo bench
cargo doc --open
cargo check # 型チェックのみ(速い)
cargo clippy # lint
cargo fmt # フォーマット
cargo update # Cargo.lock更新
cargo tree # 依存ツリー
cargo audit # 脆弱性チェック(cargo-audit)
cargo expand # マクロ展開を表示(cargo-expand)
cargo outdated # 古い依存(cargo-outdated)
cargo nextest # 高速テストランナー(cargo-nextest)
51-3. .cargo/config.toml
[build]
target-dir = "target"
jobs = 8
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=mold"] # 高速リンカ
[alias]
b = "build"
t = "test"
r = "run"
51-4. このセクションのまとめ
- rustup: バージョン管理
- cargo: 統合ビルドツール
- clippy / fmt / checkが必須
- cargo-* 拡張が豊富
- mold / lldでリンク高速化
52. パターン: ドメイン駆動設計とRust
// Newtype + Validation
struct Email(String);
impl Email {
pub fn new(s: String) -> Result<Self, ValidationError> {
if s.contains('@') {
Ok(Email(s))
} else {
Err(ValidationError::InvalidEmail)
}
}
}
// Aggregate Root
struct User {
id: UserId,
email: Email,
profile: Profile,
}
impl User {
pub fn change_email(&mut self, new: Email) -> Result<(), DomainError> {
// ドメインルール
if self.email == new { return Err(DomainError::SameEmail); }
self.email = new;
Ok(())
}
}
// Repository
trait UserRepository {
fn save(&self, user: &User) -> Result<(), RepoError>;
fn find(&self, id: UserId) -> Result<User, RepoError>;
}
// Use case(Service)
struct ChangeUserEmail<'a, R: UserRepository> {
repo: &'a R,
}
impl<R: UserRepository> ChangeUserEmail<'_, R> {
fn execute(&self, id: UserId, new_email: String) -> Result<(), AppError> {
let mut user = self.repo.find(id)?;
let email = Email::new(new_email)?;
user.change_email(email)?;
self.repo.save(&user)?;
Ok(())
}
}
Rustは DDDと非常に相性が良い。型システムがinvariantを強制し、所有権がaggregate boundaryを明確にします。
53. 大規模プロジェクト構成
my_project/
├── Cargo.toml # workspace定義
├── crates/
│ ├── domain/ # ドメインロジック
│ │ ├── Cargo.toml
│ │ └── src/
│ ├── infrastructure/ # DB / 外部API
│ │ ├── Cargo.toml
│ │ └── src/
│ ├── application/ # ユースケース
│ │ ├── Cargo.toml
│ │ └── src/
│ └── web/ # HTTPハンドラ
│ ├── Cargo.toml
│ └── src/
├── tests/ # 統合テスト
└── benches/ # ベンチマーク
# ルートCargo.toml
[workspace]
members = ["crates/*"]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["You"]
license = "MIT"
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
54. Rustの哲学を体現する例
54-1. Resultの連鎖
fn process(input: &str) -> Result<u64, AppError> {
let validated = validate(input)?;
let parsed = parse(&validated)?;
let normalized = normalize(parsed)?;
let saved = save(&normalized)?;
Ok(saved.id)
}
各ステップが失敗しうる。? で早期return、関数の流れは線形に保たれる。
54-2. traitと所有権の組み合わせ
trait Storage {
type Key;
type Value;
fn get(&self, key: &Self::Key) -> Option<&Self::Value>;
fn insert(&mut self, key: Self::Key, value: Self::Value);
}
impl Storage for HashMap<String, i32> {
type Key = String;
type Value = i32;
fn get(&self, key: &String) -> Option<&i32> {
HashMap::get(self, key)
}
fn insert(&mut self, key: String, value: i32) {
HashMap::insert(self, key, value);
}
}
traitと関連型で 抽象化 + 型安全 + ゼロコストを達成。
54-3. compile errorが指針となる
let s = String::from("hi");
let r = &s;
let m = &mut s; // エラー!「すでに不変借用がある」
println!("{}", r);
エラーメッセージが「何が問題で、どう直すべきか」を教えてくれる:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
|
let r = &s;
-- immutable borrow occurs here
let m = &mut s;
^^^^^^ mutable borrow occurs here
println!("{}", r);
- immutable borrow later used here
「戦った末に動くコード」 = 「所有権ルールを守る正しいコード」が手に入る。
55. Rustの弱点と現実
55-1. 学習曲線
最初の数週間は 「コンパイラと喧嘩する」期間。所有権・ライフタイムが直感に反することが多い。乗り越えれば天国。
55-2. コンパイル時間
ジェネリクスのモノモルフィゼーションでコンパイルが遅い。
cargo checkで型チェックだけなら速いcargo build --releaseは遅い(数分〜数十分の規模)- インクリメンタルビルドで2回目以降は速い
- mold / lldでリンク高速化
- sccacheでキャッシュ
55-3. 表現力の制約
OOPの継承・virtualはないので、OOP的設計をそのまま移植しにくい。Rustの流儀(trait + composition)に合わせて設計を変える必要がある。
55-4. 並行GUI
GUIフレームワークはまだ過渡期。Tauri、Slint、egui、icedなどがあるが、WebフロントエンドやQtと比べると未成熟。
55-5. 動的性
ランタイムでの型解析・動的コード生成は苦手。
- リフレクション: 限定的(typeidのみ)
- 動的ロード: dlopenが必要、cumbersome
56. Rustの未来
2024 edition: 着実な改善
GAT安定化: 関連型のgeneric
async fn in trait: 1.75で安定化
const generics: より柔軟な値パラメータ
specialization: nightlyで実験中
Linear types: 議論中
Effect system: 議論中
Rustは 保守的に進化を続けています。安定性を保ちつつ、必要な機能を取り込む。
産業界の採用も加速:
- Linuxカーネル
- Windowsカーネル
- AWS、Microsoft、Google、Meta、Amazon、Cloudflare、Discord
- Mozilla(Firefox)、Dropbox、Stripe
Rustの進化は、急激な破壊的変更ではなく、Editionで互換性を守りながら言語体験を改善する形で進む。これは企業採用にとって重要である。長期運用するコードでも、依存クレートとコンパイラを段階的に更新しやすい。
今後の焦点は、asyncの扱いやすさ、コンパイル時間、組み込みとLinuxカーネルでの実績、FFI、安全な抽象化の拡大にある。Rustは万能ではないが、メモリ安全性と性能を同時に求める領域では、今後も強い選択肢であり続ける。
上級: Pinとメタプログラミング
58. Pinと自己参照型
Pin<P> は 「値を動かさないことを保証する」マーカー。async fnの状態機械や自己参照構造体で必須。
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfRef {
value: String,
pointer: *const String, // valueを指す
_marker: PhantomPinned,
}
通常Rustの値は メモリ移動できますが、自己参照を持つ型は移動するとポインタが壊れます。Pin で 「これ以上動かさない」と表明。
普通のコードでは意識しませんが、asyncランタイムを書く・自己参照型を作る場合に登場します。
Pin が難しいのは、値そのものを固定するのではなく、特定のポインタ型を通じて「安全に移動できない値」として扱う点にある。多くのアプリケーションコードでは、async fn を使っていても Pin を直接書く必要はない。Futureを手で実装する、unsafeなデータ構造を作る、自己参照を扱う、という段階で初めて表に出てくる。
基本方針は、Pin と unsafe をアプリケーション全体へ広げないことである。必要な箇所を小さなモジュールに閉じ込め、外側には普通の安全なAPIを提供する。Rustの上級機能は、広く使うためではなく、危険な境界を狭く正確に扱うためにある。
59. Dropの細部
struct Resource;
impl Drop for Resource {
fn drop(&mut self) {
println!("dropping");
}
}
fn main() {
let r = Resource;
drop(r); // 明示的にドロップ
// rはここで使えない
}
Drop::drop は手動で呼べない(std::mem::drop 経由のみ)。
細かい挙動
struct A { name: &'static str }
impl Drop for A {
fn drop(&mut self) { println!("drop {}", self.name); }
}
fn main() {
let _x = A { name: "x" };
let _y = A { name: "y" };
let _z = A { name: "z" };
}
// 出力: drop z, drop y, drop x(宣言の逆順)
60. メタプログラミングのテクニック
60-1. macro_rules! の応用
macro_rules! hashmap {
( $( $k:expr => $v:expr ),* $(,)? ) => {{
let mut m = std::collections::HashMap::new();
$( m.insert($k, $v); )*
m
}};
}
let m = hashmap! {
"a" => 1,
"b" => 2,
};
Pythonのdict literal風の構文が手に入る。
60-2. proc macroでビルダ生成
// derive_builderクレート
#[derive(Builder)]
struct Request {
url: String,
method: String,
headers: Vec<(String, String)>,
}
let req = RequestBuilder::default()
.url("https://...".into())
.method("POST".into())
.build()?;
derive 1行でビルダパターンが手に入る。
61. libとbinの組み合わせ
my_project/
├── Cargo.toml
├── src/
│ ├── lib.rs # ライブラリ
│ ├── main.rs # メインバイナリ
│ └── bin/
│ ├── tool1.rs # 別バイナリ
│ └── tool2.rs
cargo run --bin tool1
cargo run --bin tool2
1つのプロジェクトで ライブラリ + 複数バイナリを提供できる。CLIツール集に最適。
62. cargo features
[features]
default = ["json"]
json = ["dep:serde_json"]
yaml = ["dep:serde_yaml"]
toml = ["dep:toml"]
all = ["json", "yaml", "toml"]
[dependencies]
serde = "1"
serde_json = { version = "1", optional = true }
serde_yaml = { version = "0.9", optional = true }
toml = { version = "0.8", optional = true }
#[cfg(feature = "json")]
pub fn parse_json(s: &str) -> Result<Value> { ... }
cargo build # default features
cargo build --no-default-features
cargo build --features yaml
cargo build --all-features
ライブラリで 「使わない機能を含めない」よう設定。
63. Rust拡張FAQ(更に追加)
Q31. dynとBox の違い
dyn Trait 自体は 「サイズ不明のトレイトオブジェクト」。直接持てないので &dyn Trait か Box<dyn Trait> で持つ。
Q32. impl Traitとdyn Trait
fn make() -> impl Iterator<Item = i32> { 1..10 } // 静的、ゼロコスト
fn make() -> Box<dyn Iterator<Item = i32>> { ... } // 動的、ヒープ
Q33. From / Intoの関係
From<X> for Y を実装すると、自動的に Into<Y> for X も実装される(blanket impl)。
let s: String = String::from("hi");
let s: String = "hi".into(); // 同等
Q34. format! の中で式
let x = 42;
let s = format!("x = {x}"); // {変数名} 直接、Rust 1.58+
let s = format!("y = {}", x); // 古い書き方、もちろんOK
Q35. let chains(Rust 1.79+)
if let Some(x) = a && let Some(y) = b { ... }
複数のパターンマッチを && で繋げる。
Q36. Editionの意味
Rust 2015 / 2018 / 2021 / 2024のような 言語のバリエーション。互換性を保ちつつ、新文法を導入。Cargo.toml の edition = "2021" で指定。
Q37. crates.io vs lib.rs
両方ともRustパッケージレジストリの参照。crates.io は公式、lib.rs は キュレーション付きで見やすい。
Q38. doc-commentの書き方
/// この関数は…
///
/// # Arguments
/// * `x` - 説明
///
/// # Returns
/// 戻り値の説明
///
/// # Examples
/// ```
/// let r = my_crate::add(1, 2);
/// assert_eq!(r, 3);
/// ```
///
/// # Panics
/// `x` が負の場合にpanic
///
/// # Errors
/// 入力が無効なら `MyError`
///
/// # Safety
/// (unsafe fnの場合)
pub fn add(x: i32) -> i32 { ... }
これらはdocs.rsで自動的にレンダリング。
Q39. 公式ドキュメント
- The Book: doc.rust-lang.org/book
- Rust by Example: doc.rust-lang.org/rust-by-example
- The Reference: doc.rust-lang.org/reference
- The Nomicon: doc.rust-lang.org/nomicon(unsafe)
- Async Book: rust-lang.github.io/async-book
- Cargo Book: doc.rust-lang.org/cargo
無料で読める高品質な公式ドキュメント。
Q40. nightly vs stable
rustup install nightly
rustup default nightly
stableは安定機能のみ、nightlyは実験的機能も使える。本番はstable、研究や言語機能の試用にnightly。
64. まとめ
Rustは 「現代に再発明されたシステムプログラミング」です。
50年以上前にCが登場し:
- システムを書ける
- パフォーマンスがある
- 安全性は手作業
40年以上後にRustが答える:
- システムを書ける
- パフォーマンスがある
- 安全性をコンパイラが保証
「安全と速度のトレードオフ」という長年の常識を覆した言語。これは技術史における 画期的な達成です。
// 最後に、Rustらしいコードを書いて終わります
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[tokio::main]
async fn main() {
let counter = Arc::new(AtomicU64::new(0));
let mut handles = vec![];
for _ in 0..10 {
let c = Arc::clone(&counter);
handles.push(tokio::spawn(async move {
for _ in 0..1000 {
c.fetch_add(1, Ordering::Relaxed);
}
}));
}
for h in handles {
h.await.unwrap();
}
println!("Counter: {}", counter.load(Ordering::Relaxed));
// 10,000が確実に出る、データレース不可能
}
このコードは:
- CPU並列で動く(複数スレッド)
- データレースなし(コンパイラが保証)
- メモリリークなし(Dropで自動解放)
- ヌルポインタなし(Option
) - ゼロコスト抽象化(手書きと同等の機械語)
Rustは、所有権、借用、ライフタイム、型システムを組み合わせて、実行時ではなくコンパイル時に多くの危険を発見する言語です。低レベル制御と安全性の両立が必要な場面では、設計段階からデータの所有者と共有範囲を明確にすることが重要になります。
まとめ
Rustは、所有権、借用、ライフタイム、型システムにより、メモリ安全性と高性能を両立させる言語です。最初はコンパイラに厳しく感じますが、その指摘はデータの所有者、共有範囲、破棄タイミングを設計するための手がかりになります。