Skip to content

Rust チートシート

安全性、速度、並行性に焦点を当てたシステム言語。

01

基礎

変数と可変性

Rust の変数はデフォルトで不変です — これは偶発的な変更を防ぐコアの安全機能です。値を本当に変更する必要がある場合のみ 'mut' を使用してください。'const' は明示的な型を必要としコンパイル時にインライン化され、'static' は固定メモリアドレスと 'static ライフタイムを持ちます。

rust
let x = 5;            // immutable by default
let mut y = 10;        // mutable with 'mut'
y += 1;                // OK, y is mutable

const MAX: u32 = 100;  // compile-time constant
static GREETING: &str = "Hi"; // global static

let z: i32 = -5;       // explicit type annotation

シャドーイング

シャドーイングにより変数名を再利用しながら型や値を変更できます。'mut' とは異なり、シャドーイングは新しいバインディングを作成します — 新しい名前を考案せずにデータを変換(例:文字列を int に解析)するのに有用です。古い値は新しいバインディング後にドロップされます。

rust
let x = 5;
let x = x * 2;         // shadow previous x, now 10
let x = "text";        // can even change type!
println!("{}", x);     // "text"

// shadowing vs mut: shadowing creates a NEW variable
// with the same name, allowing type changes

コメントと出力

アイテムドキュメントには /// を('cargo doc' で表示)、モジュール/クレートドキュメントには //! を使用します。println! は stdout へ、eprintln! は stderr へ書き込みます。{} プレースホルダはフォーマット指定をサポートします:> は右揃え、< は左揃え、^ は中央、.N は精度、#b/#o/#x は2進/8進/16進です。

rust
// Line comment
/* Block comment */
/// Doc comment (renders in rustdoc)
//! Module-level doc comment

let name = "Alice";
println!("Hello, {}!", name);        // format string
println!("{0} {1} {0}", "a", "b");   // positional args
println!("{:>5}", 42);               // right-align, width 5
println!("{:.2}", 3.14159);          // 2 decimal places: 3.14
println!("{:#b}", 0b1010);           // binary: 0b1010
eprintln!("Error to stderr");        // error output

データ型の概要

Rust には暗黙の型変換がありません — キャストには 'as' を使用します。i32 がデフォルトの整数型、f64 がデフォルトの浮動小数点型です。usize はインデックスとサイズに使用されます。char は完全な Unicode スカラー値(バイトではない)なので、'🦀' は単一の char です。

rust
// Scalar types
let a: i32 = 42;          // signed 32-bit integer
let b: u64 = 100;         // unsigned 64-bit
let c: f64 = 3.14;        // 64-bit float (default)
let d: bool = true;
let e: char = '🦀';        // 4-byte Unicode scalar

// isize/usize: pointer-sized (32 or 64 bit depending on arch)
let len: usize = vec![1,2,3].len();

// Compound types
let pair: (i32, &str) = (1, "hello");
let arr: [i32; 3] = [0; 3];   // [0, 0, 0]

型キャストとエイリアス

'as' キャストはチェックなしでデータを失う可能性があります(例:300u16 as u8 は 44 にラップ)。安全な変換には From/Into トレイトを使用し、これらは損失のないキャストに実装されています。型エイリアスは可読性を向上させますが新しい型は作成しません — 個別の newtype パターンには 'struct NewType(i32)' を使用してください。

rust
// 'as' keyword for primitive casts (may truncate)
let x: i32 = 42;
let y: f64 = x as f64;        // 42.0
let z: u8 = 300u16 as u8;     // truncated: 44

// From/Into traits for safe conversion
let s = String::from("hi");
let owned: String = "hi".into();

// Type aliases
type Kilometers = i32;
let distance: Kilometers = 5;
02

文字列

String と &str

String(所有、ヒープ)と &str(借用スライス)の区別は Rust で基本的です。テキストを所有/変更/成長させる必要がある場合は String を使用し、読み取るだけの場合は &str を使用します。&str は String のバッファまたはバイナリ内の文字列リテラルのいずれかを指せます。柔軟性のため関数パラメータでは &str を優先してください。

rust
// &str: immutable string slice (borrowed)
let s1: &str = "literal";           // stored in binary
let s2: &str = &s3[..2];            // slice of a String

// String: owned, growable, heap-allocated
let mut s3: String = String::from("hello");
s3.push_str(", world");             // append
s3.push('!');                       // append char
s3.insert(0, '>');                  // insert at index
println!("{}", s3);                 // >hello, world!

// Convert: &str -> String
let owned = "literal".to_string();
let owned2 = String::from("literal");

文字列メソッド

String メソッドはインプレースで変更する代わりに新しい所有 String を返します(可変 String の push_*/insert メソッドを除く)。split() はイテレータを返すため、.collect() で実体化します。UTF-8 境界がバイトインデックスと整列しないため、s[0] のインデックス付けは許可されません — 代わりに s.chars().nth(0) を使用してください。

rust
let s = String::from("Hello, World");

// inspection
println!("len: {}", s.len());              // 12
println!("is_empty: {}", s.is_empty());    // false
println!("contains 'World': {}", s.contains("World")); // true
println!("starts_with 'Hello': {}", s.starts_with("Hello")); // true

// transformation
let upper = s.to_uppercase();              // HELLO, WORLD
let replaced = s.replace("o", "0");        // Hell0, W0rld
let trimmed = "  hi  ".trim().to_string(); // hi

// splitting
for word in s.split(", ") {
    println!("{}", word); // Hello / World
}
let parts: Vec<&str> = s.split_whitespace().collect();

フォーマットと連結

+ 演算子は左の String の所有権を取り、右(&str)を借用します。これが s1 + &s2 の後 s1 が無効になる理由です。複数文字列の連結には format! を優先してください — より読みやすく、どのオペランドも移動しません。concat! はリテラルのみで動作し &'static str を生成します。

rust
// format! macro creates a new String
let name = "Alice";
let msg = format!("Hi {}, you have {} messages", name, 5);

// concatenation
let s1 = String::from("Hello");
let s2 = String::from(" World");
let s3 = s1 + &s2;        // s1 moved, s2 borrowed
// s1 is now invalid!

let s4 = format!("{}{}", s2, s3); // neither moved

// concat! macro for literals
let s5 = concat!("foo", "bar"); // "foobar" at compile time

文字とバイトの反復

Rust の文字列は UTF-8 エンコードのため、バイトインデックス != 文字インデックスです。chars() は Unicode スカラー値を反復し(デコードに O(n))、bytes() は生バイトを反復します。[n..m] でのスライスは n または m がマルチバイト文字の中間にある場合パニックします。バイトレベルアクセスには as_bytes() で Vec<u8> に変換してください。

rust
let s = "héllo";  // é is 2 bytes in UTF-8

// iterate over chars (Unicode scalar values)
for c in s.chars() {
    print!("{} ", c);  // h é l l o
}

// iterate over bytes
for b in s.bytes() {
    print!("{} ", b);  // 104 195 169 108 108 111
}

// get char at index (O(n) — must walk UTF-8)
let third = s.chars().nth(2); // Some('l')

// string slices must be on char boundaries
let slice = &s[0..1]; // "h" — OK
// let bad = &s[0..2]; // PANIC if mid-é!

解析と変換

parse() は文字列が有効な数値でない可能性があるため Result を返します — 常にエラーを処理してください。ターボフィッシュ構文 parse::<T>() で型をインライン指定できます。String から &str への変換は無料(単なる借用)ですが、&str から String への変換はアロケートします。collect() で char のイテレータから String を構築できます。

rust
// String/str -> number
let n: i32 = "42".parse().unwrap();
let n2 = "42".parse::<i32>().unwrap(); // turbofish syntax
let f: f64 = "3.14".parse().unwrap();

// number -> String
let s = 42.to_string();
let s2 = format!("{}", 42);

// String -> &str (free, just dereference)
let owned = String::from("hi");
let borrowed: &str = &owned;

// collect chars into String
let upper: String = "hello".chars().map(|c| c.to_uppercase().next().unwrap()).collect();
03

データ構造

配列とスライス

配列 [T; N] はコンパイル時に既知の固定サイズでスタックに存在します。スライス &[T] は連続するシーケンスを借用するファットポインタ(ポインタ + 長さ)です — 関数がサイズを問わず任意の配列やベクタを受け取ることを可能にします。汎用性のため関数シグネチャにはスライスを使用してください。

rust
// Fixed-size array (stack allocated)
let arr: [i32; 3] = [1, 2, 3];
let zeros = [0; 5];           // [0, 0, 0, 0, 0]
println!("first: {}", arr[0]);
println!("len: {}", arr.len());

// Slice: a view into an array/vector
let slice: &[i32] = &arr[1..3];  // [2, 3]
let full: &[i32] = &arr;          // whole array
let first = &arr[..1];            // [1]

// iterating
for n in &arr {
    println!("{}", n);
}

ベクタ(Vec<T>)

Vec<T> は Rust の成長可能な配列で、容量が倍増するヒープメモリに支えられています。push/pop は償却 O(1)、insert/remove は要素がシフトするため O(n) です。安全なアクセスには v[i] の代わりに .get(i) を使用してください(Option を返す)。into_iter() はベクタを消費し、所有値を生成します。

rust
// growable heap array
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3];          // macro shorthand

v.push(4);                        // append
v.pop();                          // remove last -> Option
v.insert(0, 0);                   // insert at index (O(n))
v.remove(0);                      // remove at index (O(n))
v.extend([5, 6]);                 // append multiple

// access
println!("{}", v[0]);             // panics if out of bounds
println!("{:?}", v.get(0));       // Some(&4) — safe

// iterate by value, ref, or mut ref
for n in &v { print!("{}", n); }
for n in &mut v { *n *= 2; }      // double each
let owned: Vec<i32> = v.into_iter().collect();

HashMap と BTreeMap

HashMap はハッシュ化により平均 O(1) アクセスを提供しますが順序付けなし。BTreeMap は B ツリーで O(log n) アクセスだがキーをソート済みに保ちます。高速ルックアップには HashMap を、順序付き反復や範囲クエリには BTreeMap を使用します。entry().or_insert() が 'upsert' の慣用的な方法 — 値への可変参照を返し、不在ならデフォルトを挿入します。

rust
use std::collections::HashMap;
use std::collections::BTreeMap;

// HashMap: O(1) average lookup, unordered
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 10);
scores.entry("Bob".into()).or_insert(0); // insert if absent
let alice = scores.get("Alice"); // Some(&10)

// iterate (unordered)
for (name, score) in &scores {
    println!("{}: {}", name, score);
}

// BTreeMap: O(log n) lookup, sorted by key
let mut bt: BTreeMap<String, i32> = BTreeMap::new();
bt.insert("zebra".into(), 1);
bt.insert("apple".into(), 2);
// iterates in sorted order: apple, zebra

HashSet と BTreeSet

HashSet は O(1) メンバーシップチェックで一意の値を格納します。集合演算(和、積、差、対称差)はイテレータを返します。重複排除、メンバーシップテスト、数学的集合演算にセットを使用します。BTreeSet は BTreeMap に支えられたソート済みの等価物です。

rust
use std::collections::HashSet;

let mut a: HashSet<i32> = [1, 2, 3].into_iter().collect();
let b: HashSet<i32> = [3, 4, 5].into_iter().collect();

a.insert(4);
a.remove(&1);
println!("contains 2: {}", a.contains(&2));

// set operations
let union: HashSet<_> = a.union(&b).copied().collect();
let inter: HashSet<_> = a.intersection(&b).copied().collect();
let diff: HashSet<_> = a.difference(&b).copied().collect();
let sym: HashSet<_> = a.symmetric_difference(&b).copied().collect();

タプルと分割代入

タプルは潜在的に異なる型の固定数の値をグループ化します。.0、.1 などでフィールドにアクセスします。let パターンでの分割代入が慣用的です。ユニット型 () は単一の値 () を持ち、他の言語の void のように使用されます。タプルは関数から複数の値を返すために一般的に使用されます。

rust
// tuples can hold different types
let tup: (i32, f64, &str) = (42, 3.14, "hi");

// access by index
println!("{} {} {}", tup.0, tup.1, tup.2);

// destructuring
let (n, pi, s) = tup;
println!("{} {} {}", n, pi, s);

// nested
let ((a, b), c) = ((1, 2), 3);

// unit tuple (empty)
let unit: () = ();

// function returning multiple values
fn divmod(a: i32, b: i32) -> (i32, i32) {
    (a / b, a % b)
}
let (q, r) = divmod(17, 5); // (3, 2)
04

制御フロー

If / Else If / Else

多くの言語とは異なり、Rust の 'if' は値を返す式です。これにより三項演算子(condition ? a : b)が不要になります — if/else を使用するだけです。両ブランチは同じ型を返す必要があります。条件に括弧は不要ですが、本体はブロック { } でなければなりません。

rust
let score = 85;

// if is an expression — returns a value
let grade = if score >= 90 {
    "A"
} else if score >= 80 {
    "B"
} else if score >= 70 {
    "C"
} else {
    "F"
};
println!("Grade: {}", grade);

// arms must return same type
// let bad = if true { 1 } else { "two" }; // ERROR!

ループ:loop、while、for

loop は無限ループを作成し — break で終了し値を返せます(break value)。while は各反復前に条件をチェックします。for が最も一般的なループで、範囲、配列、ベクタ、イテレータを反復します。次の反復にスキップするには 'continue' を使用します。範囲:a..b は排他的、a..=b は包括的です。

rust
// loop: infinite, use break to exit
let mut count = 0;
let result = loop {
    count += 1;
    if count == 10 {
        break count * 2; // break with value
    }
};
println!("{}", result); // 20

// while: condition-checked loop
let mut n = 5;
while n > 0 {
    println!("{}", n);
    n -= 1;
}

// for: iterate over anything with IntoIterator
for i in 0..5 { print!("{}", i); }      // 01234
for i in (1..=3).rev() { print!("{}", i); } // 321
for ch in "hello".chars() { print!("{}", ch); }

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

match は Rust の強力なパターンマッチング構造です。網羅的でなければなりません(全可能性をカバー)— キャッチオールに _ を使用します。パターンはリテラル、範囲(..=)、or パターン(|)、バインディング、ガード(if)をサポートします。match は式で値を返します。Option や Result のような列挙型を処理する慣用的な方法です。

rust
let coin = 25;
match coin {
    25 => println!("quarter"),
    10 => println!("dime"),
    5 => println!("nickel"),
    1 => println!("penny"),
    _ => println!("unknown"),  // catch-all (required)
}

// matching with bindings
let x = 3;
match x {
    1 | 2 => println!("one or two"),   // or-pattern
    3..=9 => println!("single digit"),  // range
    n if n % 2 == 0 => println!("even: {}", n), // guard
    _ => println!("other"),
}

// match is exhaustive — all cases must be covered

If Let と While Let

if let は1つのバリアントのみ気にする場合の match の糖衣構文です。match より簡潔ですが網羅性は劣ります — 他のケースが重要でない場合に使用します。while let はパターンがマッチする限りループし、イテレータでよく使用されます(next() は Option を返す)。else ブランチはオプショナルです。

rust
// if let: shorthand for matching one pattern
let maybe: Option<i32> = Some(5);

// verbose: match
match maybe {
    Some(x) => println!("got {}", x),
    None => println!("nothing"),
}

// concise: if let
if let Some(x) = maybe {
    println!("got {}", x);
} else {
    println!("nothing");
}

// while let: loop while pattern matches
let mut iter = vec![1, 2, 3].into_iter();
while let Some(n) = iter.next() {
    println!("{}", n);
}

Break、Continue とラベル

ラベル(シングルクォート + 名前)でネストされたループ内から外側のループを break または continue できます。複数のループレベルを一度に終了する必要がある場合に不可欠です。ラベルなしでは break/continue は最内ループのみに影響します。ラベルはフラグ変数のクリーンな代替です。

rust
// continue: skip to next iteration
for i in 0..10 {
    if i % 2 == 0 { continue; }
    println!("{}", i); // prints odd numbers
}

// break: exit loop
for i in 0..10 {
    if i == 5 { break; }
    println!("{}", i); // prints 0..4
}

// labeled loops (for nested break/continue)
'outer: for i in 0..3 {
    for j in 0..3 {
        if i == 1 && j == 1 {
            break 'outer; // breaks the outer loop
        }
        println!("{} {}", i, j);
    }
}
05

関数とクロージャ

関数の定義

関数は 'fn' キーワードを使用します。セミコロンなしの最後の式が戻り値です(式)。セミコロンを付けると () を返す文になります。早期終了にのみ明示的な 'return' を使用します。! 戻り値型は決して返さない発散関数(無限ループ、パニック、プロセス終了)をマークします。

rust
// basic function with return type
fn add(a: i32, b: i32) -> i32 {
    a + b  // no semicolon = expression = return value
}

// statements (with semicolon) return ()
fn greet(name: &str) {
    println!("Hi, {}", name);
    // implicit return ()
}

// explicit return
fn abs(x: i32) -> i32 {
    if x < 0 {
        return -x;  // early return needs 'return'
    }
    x  // tail expression
}

// diverging function (never returns)
fn forever() -> ! {
    loop {}
}

パラメータと引数

Rust には関数オーバーロードやオプショナルパラメータがありません(代わりにジェネリクスやビルダーを使用)。パラメータ型を慎重に選択:読み取りには &T、書き込みには &mut T、所有権移転には T。スライス(&[T])が可変長シーケンスを受け取る慣用的な方法です。デフォルト引数はサポートされません — ビルダーパターンまたは Option<T> を使用してください。

rust
// immutable borrow
fn len(s: &String) -> usize { s.len() }

// mutable borrow
fn push(v: &mut Vec<i32>) { v.push(42); }

// take ownership
fn consume(s: String) { println!("{}", s); }

// multiple return via tuple
fn swap(a: i32, b: i32) -> (i32, i32) { (b, a) }

// variadic-ish via slices
fn sum(nums: &[i32]) -> i32 {
    nums.iter().sum()
}
println!("{}", sum(&[1, 2, 3, 4])); // 10

クロージャ

クロージャは環境をキャプチャできる匿名関数です。使用法から推論されます。クロージャはデフォルトで参照でキャプチャし、'move' は所有権移転を強制します(スレッドに不可欠)。クロージャは Fn(借用)、FnMut(可変借用)、FnOnce(消費)トレイトを実装し、関数パラメータとして渡すことを可能にします。

rust
// closure syntax: |params| body
let add = |a, b| a + b;
println!("{}", add(1, 2)); // 3

// type annotations (rarely needed)
let square = |x: i32| -> i32 { x * x };

// capturing environment
let multiplier = 3;
let multiply = |x| x * multiplier; // borrows multiplier
println!("{}", multiply(5)); // 15

// move closure: takes ownership of captured vars
let name = String::from("Alice");
let greet = move || println!("Hi {}", name);
// name is now moved into greet
greet();

高階関数とイテレータ

Rust イテレータは遅延です — .collect() や別の消費メソッドが呼ばれるまで操作は実行されません。これによりゼロコスト抽象化が可能になります:コンパイラがチェーンされたイテレータメソッドを効率的なループに最適化できます。一般的なメソッド:map(変換)、filter(選択)、fold(蓄積)、take(制限)、skip、enumerate、zip、flat_map。

rust
let nums = vec![1, 2, 3, 4, 5];

// map: transform each element
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();

// filter: keep elements matching predicate
let evens: Vec<&i32> = nums.iter().filter(|&&x| x % 2 == 0).collect();

// fold/reduce: accumulate
let sum: i32 = nums.iter().sum();              // 15
let product: i32 = nums.iter().product();       // 120
let combined = nums.iter().fold(0, |acc, x| acc + x);

// chain multiple operations (lazy!)
let result: Vec<i32> = nums.iter()
    .filter(|&&x| x > 1)
    .map(|&x| x * x)
    .collect(); // [4, 9, 16, 25]

関数ポインタとトレイト

fn(小文字)は関数ポインタ型です — ゼロコストですが環境をキャプチャできません。キャプチャするクロージャにはジェネリック <F: Fn(...)> 境界を使用します。Fn は借用、FnMut は可変借用、FnOnce は消費します。関数ポインタは構造体に関数を格納や C に渡すのに有用です。Fn 境界付きジェネリクスはより柔軟で、単相化時もゼロコストです。

rust
// function pointer type
type MathFn = fn(i32, i32) -> i32;

fn add(a: i32, b: i32) -> i32 { a + b }
fn mul(a: i32, b: i32) -> i32 { a * b }

fn apply(f: MathFn, a: i32, b: i32) -> i32 {
    f(a, b)
}
println!("{}", apply(add, 3, 4)); // 7
println!("{}", apply(mul, 3, 4)); // 12

// generic over Fn trait (accepts closures too)
fn apply_fn<F: Fn(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}
let closure = |a, b| a - b;
println!("{}", apply_fn(closure, 10, 3)); // 7
06

所有権と借用

所有権ルール

所有権は Rust のコアのメモリ管理システムです — ガベージコレクタが不要。ヒープ値(String、Vec)を代入すると所有権が移動し、古い変数は無効になります。スタック型(i32、f64、bool、char、Copy 型のタプル)は Copy を実装し複製されます。これによりコンパイル時に use-after-free と double-free バグを排除します。

rust
// Rule 1: Each value has ONE owner
let s1 = String::from("hello");
let s2 = s1;  // s1's ownership MOVED to s2
// println!("{}", s1); // ERROR: s1 is invalid after move

// Rule 2: When owner goes out of scope, value is dropped
{
    let s = String::from("temp");
    // s is valid here
} // s is automatically dropped (memory freed)

// Rule 3: Copy types (i32, bool, char, etc.) are copied, not moved
let a = 5;
let b = a;  // a is copied, both valid
println!("{} {}", a, b); // OK

借用と参照

借用は所有権を取らずに値を使用できます。&T は不変参照を作成し — 同時に多数持てます。&mut T は可変参照を作成しますが — 1つの可変参照 OR 任意の数の不変参照、両方は不可。これによりコンパイル時にデータ競合を防ぎます。参照は常に有効なデータを指す必要があります(ダングリングポインタなし)。

rust
// &T: immutable borrow (read-only, multiple allowed)
fn calc_len(s: &String) -> usize {
    s.len()
    // s goes out of scope but is NOT dropped (we don't own it)
}
let s = String::from("hello");
let len = calc_len(&s);  // borrow s, don't move it
println!("'{}' has length {}", s, len); // s still valid

// &mut T: mutable borrow (exclusive, only ONE at a time)
fn push_world(s: &mut String) {
    s.push_str(", world");
}
let mut s2 = String::from("hello");
push_world(&mut s2);
println!("{}", s2); // hello, world

スライス参照

スライスはコレクションの連続部分への参照です。ポインタと長さを含む「ファットポインタ」です。文字列スライス(&str)で関数は String と文字列リテラルの両方を受け取れます。配列スライス(&[T])は任意の連続シーケンスで動作します。スライスは基となるデータを借用し、スライスが存在する間の変更やドロップを防ぎます。

rust
// string slice: &str
let s = String::from("hello world");
let hello: &str = &s[0..5];   // "hello"
let world: &str = &s[6..];    // "world"
let full: &str = &s[..];      // "hello world"

// array slice: &[T]
let arr = [1, 2, 3, 4, 5];
let mid: &[i32] = &arr[1..4]; // [2, 3, 4]

// function accepting slices (idiomatic)
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

ライフタイム

ライフタイムはコンパイラに参照がどれくらい有効かを伝えます。'a 注釈はライフタイムを変更しません — 関係を記述します。'static はプログラム全体の持続する特殊なライフタイムです(文字列リテラルが持ちます)。ほとんどのコードはライフタイム省略(コンパイラが推論)を使用します。明示的ライフタイムが必要な場合:関数が参照を返す、または構造体が参照を保持する時です。

rust
// explicit lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
// 'a means: the returned reference lives as long as
// the SHORTEST of x and y's lifetimes

let s1 = String::from("long string");
let s2 = String::from("hi");
let result = longest(s1.as_str(), s2.as_str());
println!("Longest: {}", result);

// struct holding references needs lifetime
struct Excerpt<'a> {
    part: &'a str,
}
let novel = String::from("call me Ishmael. years ago...");
let first_sentence = novel.split('.').next().unwrap();
let ex = Excerpt { part: first_sentence };

スマートポインタ

Box<T> はデータをヒープに移動(単一所有者)。Rc<T> は参照カウントで共有所有権を可能にします(シングルスレッドのみ)。Arc<T> はアトミックを使用したスレッドセーフ版。RefCell<T> は借用チェックをランタイムに移動し、共有参照を通じた変更を可能にします(内部可変性)。再帰型には Box、グラフ構造には Rc/Arc、共有データの変更には RefCell を使用します。

rust
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;

// Box<T>: heap allocation, single owner
let b = Box::new(5); // 5 lives on heap
println!("{}", b);   // dereferenced automatically

// Rc<T>: reference counting, multiple owners (single-threaded)
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a); // increments ref count
println!("count: {}", Rc::strong_count(&a)); // 2

// Arc<T>: atomic Rc, thread-safe
let arc = Arc::new(vec![1, 2, 3]);

// RefCell<T>: interior mutability (runtime borrow check)
let cell = RefCell::new(5);
*cell.borrow_mut() += 1; // mutable borrow checked at runtime
07

構造体、列挙型とトレイト

構造体の定義

構造体は関連フィールドをグループ化します。名前付きフィールド構造体が最も一般的。タプル構造体はフィールド名が意味を持たない場合(Color、Point)に有用。ユニット構造体はデータがなくトレイトの実装に使用されます。.. 構文で別インスタンスから未指定フィールドをコピーします。構造体はヒープ型(String、Vec、Box)を含まない限りスタック割り当てです。

rust
// named-field struct
struct User {
    name: String,
    age: u32,
    active: bool,
}

let u = User {
    name: String::from("Alice"),
    age: 30,
    active: true,
};

// field init shorthand
let name = String::from("Bob");
let u2 = User { name, age: 25, active: true };

// update syntax (copy remaining fields from another)
let u3 = User { age: 40, ..u2 };

// tuple struct
struct Color(u8, u8, u8);
let red = Color(255, 0, 0);

// unit struct (no fields, useful for traits)
struct AlwaysEqual;

impl によるメソッド

メソッドは impl ブロック内にあります。&self は不変借用、&mut self は可変借用、self は所有権を取ります(消費)。関連関数(self パラメータなし)は静的メソッドのようなもので — Type::function() で呼び出します。Self は型のエイリアスです。複数の impl ブロックが許可され、関心事や条件コンパイルでメソッドを分割するのに有用です。

rust
struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    // associated function (constructor, no &self)
    fn new(w: f64, h: f64) -> Self {
        Rectangle { width: w, height: h }
    }

    // method (borrows self)
    fn area(&self) -> f64 {
        self.width * self.height
    }

    // mutable method
    fn scale(&mut self, factor: f64) {
        self.width *= factor;
        self.height *= factor;
    }

    // consuming method (takes ownership)
    fn into_square(self) -> Rectangle {
        let side = (self.width + self.height) / 2.0;
        Rectangle { width: side, height: side }
    }
}

let mut r = Rectangle::new(10.0, 5.0);
println!("Area: {}", r.area()); // 50
r.scale(2.0);
let sq = r.into_square(); // r consumed

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

Rust の列挙型は代数的データ型です — 各バリアントが異なるデータを保持できます。これにより C の列挙型よりはるかに強力になります。match によるパターンマッチングでバリアントを分解しデータを抽出します。値がいくつかの異なる形式のいずれかになる場合に列挙型を使用します。matches! マクロは単一パターンマッチングで bool を返す省略記法です。

rust
// enum with data (algebraic data type)
enum Message {
    Quit,                          // no data
    Move { x: i32, y: i32 },       // named fields
    Write(String),                 // tuple variant
    ChangeColor(i32, i32, i32),    // tuple variant
}

// pattern matching with destructuring
fn process(msg: Message) {
    match msg {
        Message::Quit => println!("Quit"),
        Message::Move { x, y } => println!("Move to ({}, {})", x, y),
        Message::Write(text) => println!("Write: {}", text),
        Message::ChangeColor(r, g, b) => println!("RGB({}, {}, {})", r, g, b),
    }
}

process(Message::Move { x: 10, y: 20 });
process(Message::Write(String::from("hello")));

// enums can have methods too
impl Message {
    fn is_quit(&self) -> bool {
        matches!(self, Message::Quit)
    }
}

Option と Result

Option<T> は null を置き換え — None ケースを明示的に処理する必要があり、NullPointerException 型のバグを排除します。Result<T, E> は失敗する可能性のある操作用です。両方とも豊富なメソッドを持ちます:map(変換)、and_then(チェーン)、unwrap_or(デフォルト)、is_some/is_ok(チェック)。Result の ? 演算子はエラーを自動伝播します。これら2つの型が Rust のエラーハンドリングの骨格です。

rust
// Option<T>: Some(value) or None (replaces null)
fn find_user(id: i32) -> Option<String> {
    if id == 1 { Some(String::from("Alice")) }
    else { None }
}

let user = find_user(1);
match user {
    Some(name) => println!("Found: {}", name),
    None => println!("Not found"),
}

// convenient methods
let name = find_user(1).unwrap_or("Anonymous".into());
let upper = find_user(1).map(|n| n.to_uppercase());
let len = find_user(1).and_then(|n| Some(n.len()));

// Result<T, E>: Ok(value) or Err(error)
fn parse_num(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse()
}

match parse_num("42") {
    Ok(n) => println!("Parsed: {}", n),
    Err(e) => println!("Error: {}", e),
}

トレイトとトレイト境界

トレイトは共有振る舞いを定義します(他の言語のインターフェースのようなもの)。'impl Trait for Type' で型にトレイトを実装します。トレイトはデフォルトメソッド実装を持てます。トレイト境界(<T: Trait>)はジェネリクスを特定トレイトを実装する型に制約します。'where' 句で複雑な境界の可読性を向上させます。トレイトは静的ディスパッチ(ジェネリクス)と動的ディスパッチ(トレイトオブジェクト &dyn Trait)の両方でポリモーフィズムを可能にします。

rust
// define a trait (interface)
trait Summary {
    fn summarize(&self) -> String;

    // default method
    fn author(&self) -> String {
        String::from("Unknown")
    }
}

struct Article { title: String, content: String }

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}", self.title, self.content)
    }
    // author() uses default implementation
}

let a = Article {
    title: "Rust".into(),
    content: "Great".into(),
};
println!("{}", a.summarize());
println!("Author: {}", a.author()); // Unknown

// generic with trait bound
fn print_summary<T: Summary>(item: &T) {
    println!("{}", item.summarize());
}

// multiple bounds with +, or where clause
fn display<T: Summary + std::fmt::Display>(item: &T) {}
// or: fn display<T>(item: &T) where T: Summary + std::fmt::Display {}

Derive マクロとトレイトオブジェクト

#[derive(...)] は一般的なトレイトを自動実装します:Debug(デバッグ印刷)、Clone(ディープコピー)、PartialEq/Eq(== 比較)、Hash(HashMap キー用)、Copy(移動の代わりにスタックコピー)。トレイトオブジェクト(&dyn Trait または Box<dyn Trait>)で vtable によるランタイムポリモーフィズムを可能にし、わずかなパフォーマンスコストで。可能な場合は静的ディスパッチ(ゼロコスト)にジェネリクスを使用し、異種コレクションが必要な場合はトレイトオブジェクトを使用します。

rust
// derive common traits automatically
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone();          // Clone
println!("{:?}", p1);          // Debug
println!("{}", p1 == p2);      // PartialEq -> true

// trait object: dynamic dispatch
trait Animal {
    fn sound(&self) -> String;
}

struct Dog;
struct Cat;

impl Animal for Dog { fn sound(&self) -> String { "Woof".into() } }
impl Animal for Cat { fn sound(&self) -> String { "Meow".into() } }

// Vec of trait objects (dynamic dispatch via vtable)
let animals: Vec<Box<dyn Animal>> = vec![
    Box::new(Dog),
    Box::new(Cat),
];
for a in &animals {
    println!("{}", a.sound());
}
08

エラーハンドリング

Result と ? 演算子

? 演算子はエラーを伝播する慣用的な方法です。Ok(v) では v にアンラップし、Err(e) では関数から即座に Err(e) を返します。? は From トレイトでエラー型も変換するため、Box<dyn Error> を返す関数で任意のエラー型に ? を使用できます。Option では ? は None を早期リターンします。これにより安全性を犠牲にせずエラーハンドリングを簡潔にします。

rust
use std::fs;
use std::io;
use std::num::ParseIntError;

// ? propagates errors: if Err, return early; if Ok, unwrap
fn read_config(path: &str) -> Result<i32, io::Error> {
    let content = fs::read_to_string(path)?; // ? on io::Result
    Ok(content.len() as i32)
}

// ? converts error types via From
fn parse_and_read(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;     // io::Error -> Box
    let n: i32 = content.trim().parse()?;         // ParseIntError -> Box
    Ok(n)
}

// ? on Option too
fn first_char(s: &str) -> Option<char> {
    s.lines().next()?.chars().next()
}

パニック vs Result

回復不可能なエラー(バグ、不変条件違反)には panic! を使用します — プログラミングエラーを示します。予期される回復可能な失敗(ユーザー入力、ファイル I/O、ネットワーク)には Result を使用します。unwrap()/expect() はエラーでパニックします — テスト、プロトタイプ、または値が有効であることを証明できる場合に許容されます。本番コードでは ? と match による適切なエラーハンドリングを優先してください。

rust
// panic: unrecoverable error, crashes the program
fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Division by zero!"); // unwinds stack
    }
    a / b
}

// Result: recoverable error, caller decides
fn safe_divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

// unwrap/expect: panic on Err (use sparingly)
let n: i32 = "42".parse().unwrap();       // panics on error
let n2: i32 = "42".parse().expect("valid number"); // panic with msg

// when to panic vs Result:
// panic: bugs, invariant violations, impossible states
// Result: expected failures (file not found, parse error)

カスタムエラー型

カスタムエラー型で型安全で構造化されたエラーハンドリングが可能です。Display(人間可読)と Error(ソースチェーン用)を実装します。各基となるエラーに From を実装し ? が自動変換するようにします。thiserror(derive マクロ)や anyhow(動的エラーボックス)のようなライブラリがボイラープレートを削減します。ライブラリには thiserror を、アプリケーションには anyhow を使用してください。

rust
use std::fmt;
use std::error::Error;

#[derive(Debug)]
enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
    NotFound(String),
}

// implement Display (required by Error)
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO error: {}", e),
            AppError::Parse(e) => write!(f, "Parse error: {}", e),
            AppError::NotFound(name) => write!(f, "Not found: {}", name),
        }
    }
}

// implement Error
impl Error for AppError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            AppError::Io(e) => Some(e),
            AppError::Parse(e) => Some(e),
            _ => None,
        }
    }
}

// From impls for ? to work automatically
impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}

Result のマッチングと結合

Result コンビネータで match なしの関数型エラーハンドリングが可能です。map は Ok 値を変換し、map_err はエラーを変換し、and_then は失敗する操作をチェーンします(flatMap)。unwrap_or はエラー時にデフォルトを提供します。is_ok/is_err で消費せずにチェックします。これらのメソッドでエラーハンドリングチェーンが読みやすく、深いネストを回避します。

rust
// match on Result
let result: Result<i32, &str> = Ok(42);
match result {
    Ok(n) => println!("Got: {}", n),
    Err(e) => println!("Error: {}", e),
}

// combinators
let r1: Result<i32, &str> = Ok(5);
let doubled = r1.map(|n| n * 2);           // Ok(10)
let mapped_err = r1.map_err(|e| format!("{}", e));

// and_then: chain fallible operations
let r2 = r1.and_then(|n| if n > 0 { Ok(n * 2) } else { Err("negative") });

// unwrap_or family
let val = r1.unwrap_or(0);        // 5 or 0
let val2 = r1.unwrap_or_default(); // 5 or T::default()
let val3 = r1.unwrap_or_else(|_| 0); // lazy default

// check variants
println!("{}", r1.is_ok());  // true
println!("{}", r1.is_err()); // false

From によるエラー変換

? 演算子は From を使用してエラーを変換します。Box<dyn Error> は全標準エラーに From を実装するため、便利なキャッチオールになります。anyhow クレートは anyhow::Result を提供し、コンテキストを追加します(例:.context("failed to read config")?)。ライブラリには thiserror で特定のエラー列挙型を定義し、アプリケーションには anyhow でシンプルにしてください。

rust
use std::fs;
use std::num::ParseIntError;

// Without From: manual conversion needed
fn manual(path: &str) -> Result<i32, String> {
    let content = fs::read_to_string(path)
        .map_err(|e| e.to_string())?;  // manual convert
    let n: i32 = content.trim().parse()
        .map_err(|e| e.to_string())?;  // manual convert
    Ok(n)
}

// With Box<dyn Error>: auto-converts via From
fn boxed(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;  // auto-converts
    let n: i32 = content.trim().parse()?;      // auto-converts
    Ok(n)
}

// anyhow::Result (from anyhow crate) is ergonomic for apps
// fn anyhow_fn() -> anyhow::Result<i32> {
//     let n: i32 = something()?; // any error auto-converted
//     Ok(n)
// }
09

モジュールとクレート

モジュールシステム

Rust のモジュールシステムがコードを整理します。'mod' でモジュールを宣言(インラインまたはファイル経由)。'pub' でアイテムを公開(デフォルトはプライベート)。'use' でパスへのショートカットを作成。'pub use' でアイテムを再エクスポート(API デザインに有用)。ファイルシステムがモジュールツリーを反映:mod network は src/network.rs または src/network/mod.rs になります。クレートルートは lib.rs(ライブラリ)または main.rs(バイナリ)です。

rust
// mod.rs or mod declaration in lib.rs/main.rs
// File: src/lib.rs
mod network {
    pub mod connection {
        pub fn connect() -> bool { true }
        fn disconnect() {} // private
    }
}

// use: bring paths into scope
use network::connection::connect;

// calling with full path
fn main() {
    network::connection::connect();
    connect(); // after 'use'
}

// re-export with pub use
pub use network::connection::connect as open_connection;

// nested file: src/network/connection.rs
// mod network; in lib.rs loads src/network/mod.rs
// which can have: pub mod connection;

プライバシーと可視性

Rust のプライバシーはモジュールスコープです。アイテムはデフォルトでプライベート — 定義モジュールとその子孫内でのみアクセス可能。'pub' で公開します。'pub(crate)' は現在のクレートに制限(ライブラリ内部に有用)。'pub(super)' は親モジュールに制限。構造体フィールドは個別可視性 — pub 構造体がプライベートフィールドを持て、コンストラクタが必要です。

rust
mod my_module {
    // private by default
    fn internal_helper() {}

    // public: accessible from outside
    pub fn public_api() {
        internal_helper(); // can call private within module
    }

    // pub(crate): visible within this crate only
    pub(crate) fn crate_wide() {}

    // pub(super): visible to parent module
    pub(super) fn parent_visible() {}

    // struct fields are private even if struct is pub
    pub struct Config {
        pub name: String,    // public field
        secret: String,      // private field
    }

    // enum variants inherit the enum's visibility
    pub enum Status {
        Active,  // public because Status is public
        Inactive,
    }
}

use 文とエイリアシング

use 文でアイテムをスコープに持ち込み、パスの冗長性を削減します。グループ化インポート(use std::io::{self, Read})が複数行よりクリーンです。メソッドを使用するにはトレイトがスコープ内にある必要があります — これが Read を名前で参照しなくても 'use std::io::Read' が必要なことがある理由です。グロブインポート(*)はプレリュード以外では推奨されません。

rust
// basic use
use std::collections::HashMap;
use std::fs::read_to_string;

// grouped use
use std::io::{self, Read, Write, BufRead};
// equivalent to:
// use std::io;
// use std::io::Read;
// use std::io::Write;
// use std::io::BufRead;

// aliasing with 'as'
use std::collections::HashMap as Map;

// glob import (use sparingly)
use std::prelude::v1::*;

// bringing trait methods into scope
use std::io::Read; // now .read() method is available
let mut f = std::fs::File::open("x")?;
let mut buf = String::new();
f.read_to_string(&mut buf)?; // Read trait method

Cargo と外部クレート

Cargo は Rust のパッケージマネージャとビルドシステムです。依存関係は Cargo.toml の [dependencies] に記述します。features でオプション機能を有効化(コンパイル時間/バイナリサイズを削減)。'cargo add' が Cargo.toml を自動編集。クレートは crates.io に公開されます。Edition(2021)が言語機能を制御。Cargo がコンパイル、テスト、ドキュメント生成、公開を処理します。

rust
# Cargo.toml
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"

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

# in Rust code
use serde::{Serialize, Deserialize};
use rand::Rng;

#[derive(Serialize, Deserialize)]
struct User { name: String, age: u32 }

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

# CLI commands:
# cargo new my_app      # create new project
# cargo build           # compile
# cargo run             # compile + run
# cargo test            # run tests
# cargo add serde       # add dependency
# cargo update          # update dependencies

テスト

テストは #[test] 属性を使用します。assert_eq!/assert_ne! で値を比較。#[should_panic] でパニック発生を検証。テストはエラーベースのアサーション用に Result を返せます。#[cfg(test)] でテストモジュールがテスト時のみコンパイルされることを保証。ユニットテストはコードと共に、統合テストは tests/ ディレクトリに。'cargo test' で実行。不安定なテストをスキップするには #[ignore] を使用します。

rust
// Unit tests in same file (convention: tests module)
pub fn add(a: i32, b: i32) -> i32 { a + b }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
        assert_ne!(add(2, 3), 6);
    }

    #[test]
    fn test_with_message() {
        let result = add(1, 1);
        assert_eq!(result, 2, "Expected 2, got {}", result);
    }

    #[test]
    #[should_panic(expected = "division by zero")]
    fn test_panic() {
        panic!("division by zero");
    }

    #[test]
    fn test_result() -> Result<(), String> {
        if add(2, 2) == 4 { Ok(()) }
        else { Err(String::from("math is broken")) }
    }
}

// Integration tests: tests/integration_test.rs
// Run: cargo test
10

並行性とファイル I/O

スレッド

std::thread::spawn が OS スレッドを作成します。変数をキャプチャする場合クロージャは 'move' でなければならず、所有権をスレッドに移転します(use-after-free を防ぐ)。join() はスレッド完了までブロックし、Result を返します。Rust の所有権システムがコンパイル時にデータ競合を防ぎます — 同期(Arc<Mutex<T>>)なしでスレッド間で可変データを共有できません。

rust
use std::thread;
use std::time::Duration;

// spawn a thread
let handle = thread::spawn(|| {
    for i in 0..5 {
        println!("spawned thread: {}", i);
        thread::sleep(Duration::from_millis(10));
    }
});

// main thread continues
for i in 0..3 {
    println!("main thread: {}", i);
}

// wait for spawned thread to finish
handle.join().unwrap();

// move closure: transfer ownership to thread
let data = vec![1, 2, 3];
let h = thread::spawn(move || {
    println!("got data: {:?}", data); // data moved here
});
h.join().unwrap();

チャネル(メッセージパッシング)

チャネルでメッセージパッシングによるスレッド通信が可能です(Go のモットー:「通信によってメモリを共有」)。mpsc は複数送信者(tx.clone())だが1受信者を許可。send() は Result を返します(受信者ドロップで Err)。受信者は Iterator を実装するため for ループが自然に動作します。複数受信者には crossbeam-channel または非同期チャネルを使用します。メッセージパッシングは共有可変状態の複雑さを回避します。

rust
use std::sync::mpsc;
use std::thread;

// mpsc: multiple producer, single consumer
let (tx, rx) = mpsc::channel();

// clone transmitter for multiple producers
let tx2 = tx.clone();

thread::spawn(move || {
    let vals = vec!["a", "b", "c"];
    for v in vals {
        tx.send(v).unwrap();
    }
});

thread::spawn(move || {
    tx2.send("from tx2").unwrap();
});

// receiver is an iterator
for received in rx {
    println!("Got: {}", received);
}

Mutex と Arc(共有状態)

Arc(Atomic Reference Counted)でスレッド間の共有所有権が可能(スレッドセーフな Rc)。Mutex は排他アクセスを提供 — lock() は取得までブロックし、ドロップでロックを解放する MutexGuard を返します。RwLock は複数リーダーまたは1ライターを許可。Arc<Mutex<T>> の組み合わせが共有可変状態の標準パターン。lock() の unwrap() でポイズン(ロック保持中のスレッドパニック)を処理します。

rust
use std::sync::{Arc, Mutex};
use std::thread;

// Arc: thread-safe reference counting
// Mutex: mutual exclusion lock
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        // lock() returns MutexGuard, auto-unlocks when dropped
        let mut num = counter.lock().unwrap();
        *num += 1;
    }); // lock released here
    handles.push(handle);
}

for h in handles { h.join().unwrap(); }

println!("Result: {}", *counter.lock().unwrap()); // 10

// RwLock: multiple readers OR one writer
use std::sync::RwLock;
let data = RwLock::new(5);
let r1 = data.read().unwrap();  // shared read
let r2 = data.read().unwrap();
// let w = data.write().unwrap(); // would block until r1, r2 dropped

ファイル I/O

fs::read_to_string は小さなファイルに便利です。大きなファイルにはシステムコールを削減するため BufReader/BufWriter を使用します。Read/Write トレイトが低レベルバイト操作を提供。BufRead がテキスト用の lines() と read_line() を追加。常に ? でエラーを処理してください(ファイルが見つからない、権限拒否、ディスク満杯)。flush() でバッファデータを OS に確保します(ディスクまでは保証しません)。

rust
use std::fs;
use std::io::{Read, Write, BufReader, BufWriter};
use std::fs::File;

// read entire file
let content = fs::read_to_string("file.txt")?;
fs::write("output.txt", "Hello")?;

// buffered read (efficient for large files)
let file = File::open("file.txt")?;
let mut reader = BufReader::new(file);
let mut buf = String::new();
reader.read_to_string(&mut buf)?;

// line by line
use std::io::BufRead;
let file = File::open("file.txt")?;
for line in BufReader::new(file).lines() {
    println!("{}", line?);
}

// buffered write
let file = File::create("out.txt")?;
let mut writer = BufWriter::new(file);
writeln!(writer, "Line 1")?;
writeln!(writer, "Line 2")?;
writer.flush()?; // ensure written to disk

Async/Await(Tokio)

Async/await で OS スレッドなしの効率的な並行性が可能 — タスクはスレッドプールで実行され .await ポイントで yield します。tokio が最も人気のある非同期ランタイム。async fn は .await される必要がある Future を返します。tokio::join! が future を並行実行し全てを待機。tokio::select! が future を競争させます。非同期は I/O バウンド作業(ネットワーク、ファイル)に理想です。CPU バウンド作業にはスレッドを使用します。.await はスレッドをブロックしません — ランタイムに制御を戻します。

rust
use tokio::{fs, task, time};
use std::time::Duration;

// async fn returns a Future
async fn fetch_data() -> String {
    time::sleep(Duration::from_secs(1)).await;
    String::from("data")
}

// spawn concurrent tasks
#[tokio::main]
async fn main() {
    // run tasks concurrently
    let (a, b, c) = tokio::join!(
        fetch_data(),
        fetch_data(),
        fetch_data()
    );
    println!("{} {} {}", a, b, c);

    // spawn a background task
    let handle = tokio::spawn(async {
        let data = fs::read_to_string("file.txt").await.unwrap();
        println!("Read {} bytes", data.len());
    });
    handle.await.unwrap();

    // select: first to complete wins
    tokio::select! {
        val = fetch_data() => println!("Got: {}", val),
        _ = time::sleep(Duration::from_millis(500)) => {
            println!("Timeout!");
        }
    }
}
11

ライフタイムの深掘り

関数内の名前付きライフタイム

ライフタイムはコンパイラが参照の有効性を追跡する方法です。'a はジェネリックライフタイムパラメータです — ランタイム動作を変更せず、コンパイル時チェックのみ。関数が複数の参照を取り1つを返す場合、コンパイラに返された参照が入力より長生きしないことを知らせるためライフタイムを注釈する必要があります。これによりコンパイル時にダングリングポインタを防ぎます。

rust
// Lifetimes tell the compiler how long references live
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
// 'a means: the returned reference lives as long as
// the SHORTEST of x and y

let s1 = String::from("long string");
let result;
{
    let s2 = String::from("hi");
    result = longest(s1.as_str(), s2.as_str());
    // result valid here
    println!("{}", result);
}
// result NOT valid here — s2 is dropped

ライフタイム省略ルール

ライフタイム省略ルールで一般的なケースで明示的なライフタイム注釈を省略できます。ルール1は各参照パラメータに個別のライフタイムを割り当て。ルール2は入力参照が1つの場合、そのライフタイムを出力に割り当て。ルール3はメソッドに適用 — 出力は &self のライフタイムを取得。これらが全参照を解決しない場合、明示的ライフタイムを書く必要があります。ほとんどの慣用的な Rust コードは明示的ライフタイム注釈をほとんど必要としません。

rust
// The compiler applies 3 elision rules automatically:
// 1. Each input reference gets its own lifetime
// 2. If one input lifetime, output gets that lifetime
// 3. If &self/&mut self, output gets self's lifetime

// These DON'T need explicit annotations (rule 2):
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' { return &s[0..i]; }
    }
    &s[..]
}

// This NEEDS annotation (multiple inputs, rule doesn't apply):
// fn longest<'a>(x: &'a str, y: &'a str) -> &'a str

'static ライフタイム

'static は最長のライフタイムです — プログラム全体の期間持続します。全ての文字列リテラルはバイナリに埋め込まれているためこのライフタイムを持ちます。T: 'static を境界として見る場合、T が永遠に生きる必要があるのではなく — T が 'static より短い参照を含まないことを意味します(String、Vec、i64 のような所有型は常にこれを満たす)。スレッドは呼び出し関数より長生きする可能性があるため 'static を要求します。

rust
// 'static means the reference lives for the entire program
let s: &'static str = "I live forever";
// All string literals are &'static str

// Static variables (global, program-wide)
static COUNTER: AtomicUsize = AtomicUsize::new(0);
COUNTER.fetch_add(1, Ordering::SeqCst);

// 'static in bounds: T: 'static means T contains no
// non-static references (owned data always satisfies this)
fn spawn_thread<T: Send + 'static>(t: T) {
    std::thread::spawn(move || {
        drop(t);
    });
}

構造体内のライフタイム

構造体が参照(所有型ではなく)を保持する場合、その参照がどれくらい有効かを宣言するライフタイムパラメータが必要です。構造体インスタンスは借用するデータより長生きできません。これはゼロコピーパーサー、借用データ上のイテレータ、ビューで一般的です。ライフタイムは構造体とその impl ブロックの両方で宣言する必要があります。特定の理由がない限りデータを所有(String、Vec)することを優先してください。

rust
// Structs holding references need lifetime annotations
struct Parser<'a> {
    text: &'a str,
    pos: usize,
}

impl<'a> Parser<'a> {
    fn new(text: &'a str) -> Self {
        Parser { text, pos: 0 }
    }
    fn peek(&self) -> Option<char> {
        self.text[self.pos..].chars().next()
    }
    fn advance(&mut self) {
        self.pos += 1;
    }
}

// The struct cannot outlive the text it borrows
let text = String::from("hello");
let mut p = Parser::new(&text);
p.advance();

複数ライフタイムと部分型

異なる相互作用を持つ複数の参照を持つ関数は複数のライフタイムパラメータを必要とします。ライフタイム部分型付けはより長いライフタイムがより短いものの代わりになることを意味します(共変性)— &'static は &'a が期待されるどこでも使用できます。これが 'static が全ライフタイムの部分型である理由です。出力のライフタイムが一部の入力のみに依存する場合に複数ライフタイムを使用し、コンパイラに柔軟性を、呼び出し元に制約の削減を与えます。

rust
// Multiple lifetime parameters
fn parse<'src, 'ctx>(src: &'src str, ctx: &'ctx Context) -> &'src str {
    // returns something tied to src, not ctx
    src
}

// 'static: 'a is always true (static outlives everything)
fn static_ref<'a>(_: &'a str) -> &'static str {
    "constant"  // 'static coerces to 'a
}

// Variance: &'long can be used where &'short is expected
// (covariance) — longer lifetime is a subtype
fn use_ref<'short>(r: &'short str) {}
let long: &'static str = "hi";
use_ref(long);  // OK: 'static coerces to any 'short
12

スマートポインタ(Box、Rc、Arc、RefCell)

Box<T> — ヒープ割り当て

Box<T> は Rust の最もシンプルなスマートポインタです — 単一所有権で値をヒープ割り当てします。再帰型(サイズがコンパイル時に分からない)、スタックで移動したくない大きな値、動的ディスパッチ用のトレイトオブジェクト(Box<dyn Trait>)に使用します。Box は T にデリファレンスするため内部値のように振る舞います。ヒープ割り当て自体を除いて実質的にゼロオーバーヘッドです。

rust
// Box moves data to the heap (single owner)
let b = Box::new(5);
println!("{}", b);  // derefs automatically

// Recursive types NEED Box (size unknown at compile time)
enum List {
    Cons(i32, Box<List>),
    Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

// Trait objects (dynamic dispatch)
let shapes: Vec<Box<dyn Draw>> = vec![
    Box::new(Circle { radius: 1.0 }),
    Box::new(Square { side: 2.0 }),
];
for s in &shapes { s.draw(); }

// Box has zero runtime overhead when dereferencing

Rc<T> — 参照カウント(シングルスレッド)

Rc<T>(Reference Counted)はシングルスレッドシナリオで複数所有権を可能にします。Rc::clone はデータをコピーする代わりに参照カウントをインクリメントします — 安価なポインタコピー。最後の Rc がドロップされると値もドロップされます。共有グラフノード、親子ツリー、複数の部分が同じデータを所有する必要がある構造に使用します。Rc はスレッドセーフではありません — マルチスレッドには Arc を使用してください。Rc は不変です — 直接変更できません。

rust
use std::rc::Rc;

// Rc allows multiple owners via reference counting
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a);  // increments count, doesn't copy
let c = Rc::clone(&a);

println!("count = {}", Rc::strong_count(&a));  // 3
// Data dropped when count reaches 0

// Shared graph/tree structures
struct Node {
    children: Vec<Rc<Node>>,
    value: i32,
}
let leaf = Rc::new(Node { children: vec![], value: 1 });
let branch = Rc::new(Node {
    children: vec![Rc::clone(&leaf)],
    value: 2,
});

Arc<T> — アトミック参照カウント(スレッドセーフ)

Arc<T>(Atomically Reference Counted)は Rc のスレッドセーフな対応物です。参照カウントにアトミック操作を使用し、スレッド間で安全に共有できます。トレードオフは Rc よりわずかに多いオーバーヘッドです。複数スレッド間で共有所有権が必要な場合は常に Arc を使用します。共有データを変更するには、Mutex(排他アクセス用)または RwLock(読み取り中心アクセス用)と Arc を組み合わせます。Arc::clone は安価 — アトミックカウンタをインクリメントするだけです。

rust
use std::sync::Arc;
use std::thread;

// Arc: thread-safe version of Rc (atomic ref counting)
let data = Arc::new(vec![1, 2, 3, 4, 5]);

let handles: Vec<_> = (0..3).map(|i| {
    let data = Arc::clone(&data);  // atomic increment
    thread::spawn(move || {
        println!("Thread {} sees: {:?}", i, *data);
    })
}).collect();

for h in handles { h.join().unwrap(); }

// Arc is slightly slower than Rc due to atomic operations
// Only use Arc when actually sharing across threads

RefCell<T> — 内部可変性

RefCell<T> は内部可変性を提供します — 共有参照を通じて変更でき、借用ルールはコンパイル時の代わりにランタイムで強制されます。borrow() は不変参照を、borrow_mut() は可変参照を返します。ルール違反(例:2つの可変借用)はランタイムでパニックします。コンパイラが借用安全性を証明できない場合(グラフ構造、テストのモックオブジェクト)に RefCell を使用します。Rc<RefCell<T>> が可変共有シングルスレッドデータの古典的パターンです。

rust
use std::cell::RefCell;

// RefCell moves borrow checking to RUNTIME
let cell = RefCell::new(vec![1, 2, 3]);

// Multiple immutable borrows OR one mutable borrow
{
    let mut borrowed = cell.borrow_mut();
    borrowed.push(4);
}  // borrow released here

{
    let r1 = cell.borrow();     // OK
    let r2 = cell.borrow();     // OK — multiple immutable
    println!("{:?} {:?}", r1, r2);
}

// cell.borrow_mut() while r1 alive → PANIC at runtime
// Combine with Rc: Rc<RefCell<T>> for mutable shared graphs

Weak<T> — 参照サイクルの打破

Weak<T> は強参照カウントに影響しない非所有参照です。これは参照サイクルを打破するために不可欠です:親が子を所有(Rc)し子が親を所有(Rc)する場合、どちらも解放されません — メモリリークです。解決策はバック参照を Weak にすることです。upgrade() は Option<Rc<T>> を返します — 値が既にドロップされていれば None。child→parent リンク、キャッシュ、データを生かしておきたくないオブザーバーパターンに Weak を使用します。

rust
use std::rc::{Rc, Weak, RefCell};

// Weak references don't contribute to the strong count
// — prevents memory leaks in cycles
struct Node {
    parent: RefCell<Weak<Node>>,        // weak: child → parent
    children: RefCell<Vec<Rc<Node>>>,   // strong: parent → children
}

let leaf = Rc::new(Node {
    parent: RefCell::new(Weak::new()),
    children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
    parent: RefCell::new(Weak::new()),
    children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);

// Upgrade returns Option — parent may already be dropped
if let Some(p) = leaf.parent.borrow().upgrade() {
    println!("parent exists");
}
13

トレイトオブジェクトと動的ディスパッチ

dyn Trait — 動的ディスパッチ

dyn Trait で動的ディスパッチが可能です — 具象型はコンパイル時に消去され、メソッド呼び出しはランタイムで vtable を経由します。これにより単一コレクション(Vec<Box<dyn Animal>>)に異種型を格納できます。トレードオフ:わずかなランタイムコスト(vtable 間接化、インライン化なし)と型がコンパイル時に分からないこと。具象型のセットがコンパイル時に分からない、または異なる型をグループ化する必要がある場合にトレイトオブジェクトを使用します。

rust
trait Animal {
    fn name(&self) -> &str;
    fn sound(&self) -> String;
}

struct Dog { name: String }
struct Cat { name: String }

impl Animal for Dog {
    fn name(&self) -> &str { &self.name }
    fn sound(&self) -> String { "Woof".into() }
}
impl Animal for Cat {
    fn name(&self) -> &str { &self.name }
    fn sound(&self) -> String { "Meow".into() }
}

// dyn Trait = erased type, runtime dispatch via vtable
let animals: Vec<Box<dyn Animal>> = vec![
    Box::new(Dog { name: "Rex".into() }),
    Box::new(Cat { name: "Whiskers".into() }),
];
for a in &animals {
    println!("{} says {}", a.name(), a.sound());
}

オブジェクト安全性ルール

トレイトがオブジェクトセーフなのはコンパイラが vtable を構築できる場合のみです。2つのルール:(1) メソッドは Self を返してはならない(具象型が消去されるため分からない)、(2) メソッドはジェネリック型パラメータを持ってはならない(vtable が全ての可能な型のエントリを必要とする)。Sized をスーパートレイトとするトレイトもオブジェクトセーフではありません。オブジェクト安全性が必要な場合、Self を返すメソッドを Box<dyn Trait> を返すようにリファクタリングするか、別のファクトリ関数を使用してください。

rust
// Object-safe traits CAN be used as dyn Trait:
trait Draw {
    fn draw(&self);  // OK: &self, no generics
}

// NOT object-safe:
trait Bad {
    fn create() -> Self;        // returns Self — needs known type
    fn process<T>(&self, x: T); // generic method — vtable can't cover all T
    const SIZE: usize;          // associated const (sometimes OK)
}

// Workaround for Self returns — use a factory or Box<Self>
trait GoodFactory {
    fn new_boxed() -> Box<dyn GoodFactory>;
}

// Sized bound makes trait non-object-safe
// trait Foo: Sized {}  // NOT object-safe

トレイトオブジェクト vs ジェネリクス

ジェネリクスは単相化を使用します — コンパイラが各具象型の関数の個別コピーを生成し、静的ディスパッチと完全な最適化(インライン化)を可能にします。これはランタイムコストゼロですがバイナリサイズが増加します。トレイトオブジェクト(dyn)は vtable ルックアップ付きの単一関数を使用します — より小さいバイナリだが呼び出しごとにわずかなランタイムコスト。パフォーマンスが重要で型セットが小さい/既知の場合はジェネリクスを選択し、異種コレクションが必要な場合や全ての型を事前に知らない場合は dyn を選択してください。

rust
// Generics: monomorphization, static dispatch, zero overhead
fn max_generic<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}
// Each type instantiation creates a separate function:
// max_generic::<i32>, max_generic::<f64>, etc.

// Trait objects: dynamic dispatch, single function
fn max_dyn(a: &dyn PartialOrd, b: &dyn PartialOrd) -> bool {
    // can't return — don't know size at compile time
    a.partial_cmp(b) == Some(std::cmp::Ordering::Less)
}

// Rule of thumb:
// - Few types, performance-critical → generics (static dispatch)
// - Many/unknown types, flexibility needed → dyn (dynamic dispatch)
// - Heterogeneous collections → must use dyn

Any トレイトとダウンキャスト

Any トレイトで任意の型の値を格納し、ダウンキャストでランタイムに具象型を復元できます。downcast_ref::<T>() は Option<&T> を、downcast::<T>() は Result<Box<T>, Box<dyn Any>> を返します。これはコンパイル時に型が本当に分からない場合(プラグインシステム、動的設定)の Rust の脱出ハッチです。ただし、可能な型のセットが分かっている場合は列挙型を優先してください — より安全で速く、より慣用的です。Any は TypeId に依存し、全 'static 型に実装されます。

rust
use std::any::Any;

// Any enables runtime type checking & downcasting
let x: Box<dyn Any> = Box::new(42i32);

// downcast_ref returns Option<&T>
if let Some(n) = x.downcast_ref::<i32>() {
    println!("It's an i32: {}", n);
}

// downcast returns Option<T> (for Box)
let boxed: Box<dyn Any> = Box::new("hello");
if let Ok(s) = boxed.downcast::<&str>() {
    println!("Recovered: {}", s);
}

// Useful for: plugin systems, error types, heterogeneous storage
// Avoid overusing — prefer enums when the type set is known

デフォルトトレイトメソッドとスーパートレイト

トレイトは実装者がオーバーライドまたはそのまま使用できるデフォルトメソッド実装を提供できます。スーパートレイト(trait Named: Shape)は実装型にスーパートレイトの実装も要求します — これにより Named 型が area() と describe() を持つことが保証される階層を作成します。デフォルトメソッドはボイラープレートを削減し、トレイトにメソッドを追加すると既存の全実装者が破損なしで恩恵を受ける「拡張メソッド」パターンを可能にします。

rust
trait Shape {
    fn area(&self) -> f64;
    // Default method — can be overridden
    fn describe(&self) -> String {
        format!("Shape with area {:.2}", self.area())
    }
}

// Supertrait: trait that requires another trait
trait Named: Shape {
    fn name(&self) -> &str;
}

struct Circle { radius: f64 }
impl Shape for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.radius.powi(2) }
}
impl Named for Circle {
    fn name(&self) -> &str { "Circle" }
}

let c = Circle { radius: 2.0 };
println!("{}", c.describe());  // uses default method
14

マクロ(宣言型と手続き型)

宣言型マクロ(macro_rules!)

macro_rules! はパターンマッチングでコンパイル時に展開される宣言型マクロを作成します。$(...),* 構文は反復マッチャー — ゼロ以上のカンマ区切り式にマッチします。$x:expr は「任意の式にマッチし x にバインド」を意味します。マクロは型チェック前に展開されるため、任意の型で動作するコードを生成できます。ジェネリクスでは扱えないボイラープレート削減(可変長引数、構文拡張)にマクロを使用します。

rust
// macro_rules! defines pattern-matching macros
macro_rules! vec_of {
    ($($x:expr),*) => {{
        let mut v = Vec::new();
        $( v.push($x); )*
        v
    }};
}

let nums = vec_of!(1, 2, 3, 4);

// Recursive macro: build a HashMap
macro_rules! hashmap {
    ($($k:expr => $v:expr),*) => {{
        let mut m = std::collections::HashMap::new();
        $( m.insert($k, $v); )*
        m
    }};
}
let config = hashmap!("host" => "localhost", "port" => 8080);

マクロフラグメント型

フラグメント指定子はマクロ引数がどの種類の構文にマッチするかを決定します。:ident は識別子(名前)、:expr は式(値)、:ty は型、:block はブレース区切りブロック、:stmt は文、:literal はリテラルにマッチします。適切な指定子の選択が重要です — :expr が最も一般的ですが、関数/変数名を作成したい場合は :ident が必要です。マクロシステムは衛生的です:マクロが導入する識別子は周囲のコードと衝突しません。

rust
// Common fragment specifiers:
macro_rules! build_fn {
    // $name:ident — identifier (function/variable name)
    // $body:block — a block { ... }
    // $ty:ty — a type
    // $expr:expr — an expression
    // $stmt:stmt — a statement
    // $lit:literal — a literal (string, number)
    ($name:ident, $ret:ty, $body:block) => {
        fn $name() -> $ret $body
    };
}

build_fn!(get_answer, i32, { 42 });
println!("{}", get_answer());

// :pat — pattern, :path — module path
// :meta — attribute meta-item, :vis — visibility

組み込み標準マクロ

Rust には多くの組み込みマクロが同梱されています。println!/eprintln! は stdout/stderr に出力。dbg! は式の値をファイル/行情報付きで出力 — デバッグに最適(値も返します)。assert!/assert_eq!/assert_ne! はテストと不変条件用。todo!/unimplemented! は未完成コードをパニックでマーク。file!/line!/module! はコンパイル時位置情報を提供。env!/option_env! はコンパイル時に環境変数を読み取り — バージョン情報の埋め込みに有用です。

rust
// Formatting & printing
println!("x = {}, y = {:?}", 1, "two");
eprintln!("Error: {}", "oops");     // stderr
format!("{}-{}", "a", "b");          // returns String

// Debug helpers
dbg!(2 + 2);                          // prints [src.rs:1] 2 + 2 = 4
assert!(1 + 1 == 2);                  // panic if false
assert_eq!(2 + 2, 4);                 // panic if not equal
assert_ne!(1, 2);                     // panic if equal

// Code generation
todo!("not implemented yet");         // unimplemented!()
unimplemented!();
panic!("fatal: {}", "reason");

// Environment & file info
println!("{}:{}", file!(), line!());  // src.rs:1
println!("{}", env!("CARGO_PKG_NAME"));

手続き型マクロの概要

手続き型マクロ(proc macros)は TokenStream を入力として受け取り TokenStream を出力として生成する Rust 関数です — 完全なコードからコードへの変換。宣言型マクロとは異なり、任意の計算が可能です。3つの型:derive マクロ(#[derive] でトレイト実装を追加)、属性マクロ(アイテムに注釈)、関数風マクロ(sqlx::query! のようなカスタム構文)。proc-macro = true の別クレートに存在する必要があります。syn クレートが Rust 構文を解析し、quote! がコードを生成します。人気例:serde、tokio、thiserror。

rust
// Procedural macros are Rust functions that transform code
// Three kinds (must be in a separate crate with proc-macro=true):

// 1. Derive macros — #[derive(MyTrait)]
#[derive(Debug, Clone, MyTrait)]
struct Point { x: f64, y: f64 }

// 2. Attribute macros — #[my_attr]
#[my_attr]
fn function() {}

// 3. Function-like macros — my_macro!(...)
sqlx::query!("SELECT * FROM users");

// Cargo.toml for a proc-macro crate:
// [lib]
// proc-macro = true
//
// [dependencies]
// syn = "2.0"   # parse Rust code
// quote = "1.0" # generate code
// proc-macro2 = "1.0"

マクロ衛生性と一般的なパターン

Rust マクロは衛生的です — マクロ内で作成された識別子は別の「構文コンテキスト」に存在し、呼び出しスコープの変数を誤ってキャプチャまたはシャドウしません。これによりマクロの内部変数名が呼び出し元の変数と衝突する微妙なバグを防ぎます。stringify! は任意のトークンストリームをコンパイル時に文字列リテラルに変換します(エラーメッセージに有用)。cfg_debug! は一般的なパターンを示します:cfg! システムを使用してビルド設定に基づきコードを条件コンパイルします。

rust
// Hygiene: macro-introduced identifiers don't leak
macro_rules! using_temp {
    ($e:expr) => {
        let temp = $e;  // this 'temp' is distinct from caller's
        println!("{}", temp);
    };
}
let temp = 10;
using_temp!(temp + 5);  // no conflict — hygienic

// Conditional compilation macro
macro_rules! cfg_debug {
    ($($e:tt)*) => {
        #[cfg(debug_assertions)]
        { $($e)* }
    };
}
cfg_debug! {
    println!("Debug mode on");
}

// stringify! converts tokens to a string literal
let s = stringify!(a + b * c);  // "a + b * c"
15

Unsafe Rust

生ポインタ

生ポインタ(*const T、*mut T)は Rust の借用チェッカーからの脱出ハッチです。参照とは異なり、null にでき、エイリアス(同じデータへの複数ポインタ)でき、ライフタイムを追跡しません。作成は安全ですが、コンパイラが有効性を保証できないためデリファレンスには unsafe が必要です。FFI(C とのインターフェース)、低レベルデータ構造(リンクリスト、ベクタ)の実装、手動で安全性を保証するパフォーマンスクリティカルなコードに生ポインタを使用します。常に unsafe が健全な理由をドキュメント化してください。

rust
// Raw pointers: *const T (immutable) and *mut T (mutable)
let x = 42;
let r1: *const i32 = &x;       // coerce from reference
let r2: *mut i32 = x as *mut i32;  // cast

// Can be null, can alias, no borrow checking
let null: *const i32 = std::ptr::null();

// Creating raw pointers is safe, but DEREFERENCING is unsafe
unsafe {
    println!("r1 = {}", *r1);
}

// Convert between types (transmute-like)
let bytes: [u8; 4] = [0x78, 0x56, 0x34, 0x12];
let ptr = bytes.as_ptr() as *const u32;
unsafe { println!("0x{:x}", *ptr); }  // little-endian int

Unsafe ブロックと関数

unsafe は借用チェッカーをオフにしません — 5つの特定のことを可能にします:(1) 生ポインタのデリファレンス、(2) unsafe 関数の呼び出し、(3) unsafe トレイトの実装、(4) static mut のアクセス/変更、(5) union フィールドのアクセス。unsafe ブロックは unsafe 操作を明示的かつ局所化します。unsafe fn は関数の呼び出しがコンパイラがチェックできない不変条件の維持を必要とすることを宣言します。get_unchecked はパフォーマンスのために境界チェックをスキップします — インデックスを検証した場合のみ安全です。unsafe 表面積を最小化し安全な API の背後にカプセル化してください。

rust
// unsafe block: a localized unsafe region
let ptr: *const i32 = &42;
let val = unsafe { *ptr };

// unsafe fn: the ENTIRE function body is unsafe
unsafe fn dangerous(ptr: *const u8) -> u8 {
    *ptr
}
// Callers must use unsafe block:
let b = 5u8;
unsafe { dangerous(&b) };

// Splitting borrows safely (compiler is conservative)
let mut v = vec![1, 2, 3, 4];
let len = v.len();
unsafe {
    let first = v.get_unchecked(0);   // no bounds check
    let last = v.get_unchecked(len - 1);
    println!("{} {}", first, last);
}

FFI — C 関数の呼び出し

FFI(Foreign Function Interface)で Rust が C 関数を呼び出し、その逆も可能です。extern "C" ブロックで外部 C 関数を宣言します — コンパイラが動作を検証できないため呼び出しは unsafe です。#[no_mangle] で Rust が関数をリネームするのを防ぎ、C が名前で見つけられるようにします。#[repr(C)] で構造体レイアウトが C のメモリレイアウトに一致することを保証します(Rust は効率のためデフォルトでフィールドを並べ替える可能性あり)。システムコール、レガシーライブラリ、パフォーマンスクリティカルなバインディングに FFI を使用します。bindgen クレートが C ヘッダから FFI 宣言を自動生成します。

rust
// extern "C" declares foreign functions
extern "C" {
    fn abs(x: i32) -> i32;
}

fn main() {
    let x = -5;
    let positive = unsafe { abs(x) };
    println!("{}", positive);  // 5
}

// Exporting Rust functions to C
#[no_mangle]  // prevent name mangling
pub extern "C" fn add(a: i64, b: i64) -> i64 {
    a + b
}

// C-compatible struct
#[repr(C)]
struct Point { x: f64, y: f64 }

Unsafe トレイトの実装

Unsafe トレイト(Send、Sync など)は実装者がコンパイラが検証できない不変条件を維持することを要求します。Send は型がスレッド間で安全に移動できることを意味し、Sync は &T がスレッド間で共有できることを意味します。コンパイラはほとんどの型にこれらを自動導出しますが、生ポインタはデフォルトで Send/Sync ではありません。手動で実装する場合、スレッド安全性の責任を負います。static mut は複数スレッドが競合する可能性があるため unsafe アクセスが必要です — アトミック(AtomicU64)または Mutex を優先してください。常に SAFETY コメントで安全性の根拠をドキュメント化してください。

rust
// Some traits are unsafe to implement — the compiler can't verify invariants
use std::marker::Send;

// Send/Sync are auto-implemented, but sometimes you must manually impl
struct RawPointer<T>(*mut T);

// SAFETY: We guarantee the pointer is only used on one thread
unsafe impl<T> Send for RawPointer<T> where T: Send {}

// Splitting a slice into disjoint mutable parts
let mut v = vec![1, 2, 3, 4, 5, 6];
let (left, right) = v.split_at_mut(3);
// left = [1,2,3], right = [4,5,6] — disjoint, but compiler
// couldn't prove this without unsafe internally

// Global mutable state
static mut COUNTER: u64 = 0;
unsafe { COUNTER += 1; }  // unsafe: data races possible

Union とインラインアセンブリ

Union は異なる型が同じメモリ位置を共有することを許可します — 最後に書き込まれなかったフィールドの読み取りは未定義動作のため unsafe です。主に C との FFI 用です。transmute は同じサイズの別の型へビットパターンを再解釈キャストします — サイズが異なる場合や型に互換性がない場合は極めて危険です。インラインアセンブリ(asm!)で CPU 命令を直接埋め込み、カーネル開発や極限の最適化に有用です。これらはすべて鋭いツールです:安全な代替が存在しない場合のみ使用し、安全な抽象の背後にカプセル化してください。

rust
// Unions: multiple fields share the same memory (like C unions)
#[repr(C)]
union IntOrFloat {
    i: i32,
    f: f32,
}

let mut u = IntOrFloat { i: 42 };
unsafe { println!("as int: {}", u.i); }
u.f = 3.14;
unsafe { println!("as float: {}", u.f); }
// Reading the WRONG field is undefined behavior!

// Inline assembly (nightly / asm!)
#[cfg(feature = "asm")]
unsafe fn halt() {
    std::arch::asm!("hlt", options(nostack));
}

// Transmute: reinterpret bits as another type (same size)
let bits: u32 = 0x40490FDB;  // ~3.14159 in IEEE 754
let pi: f32 = unsafe { std::mem::transmute(bits) };
16

イテレータの深掘り

Iterator トレイトと作成

Iterator トレイトは Option<Item> を返す next() メソッドのみを要求します — None は枯渇を通知。それ以外全て(map、filter、collect)はその上に構築されます。iter() は要素を借用し(&T)、into_iter() はコレクションを消費し(所有 T を生成)、iter_mut() は &mut T を生成します。範囲(1..5、1..=5)は直接イテレータです。文字列は char(Unicode スカラー値)または byte で反復します。イテレータは遅延です — 消費するまで何も実行されません。

rust
// The Iterator trait: one required method
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ... many provided methods (map, filter, etc.)
}

// Creating iterators
let v = vec![1, 2, 3];
let iter = v.iter();        // borrows: &i32
let into_iter = v.into_iter(); // owns: i32 (consumes v)
let mut_iter = v.iter_mut();   // mutably borrows: &mut i32

// Range is an iterator
for i in 1..=5 { print!("{} ", i); }  // 1 2 3 4 5

// String iteration
for c in "héllo".chars() { print!("{} ", c); }  // h é l l o
for b in "hi".bytes() { print!("{} ", b); }      // 104 105

アダプタメソッド(遅延)

アダプタメソッドはイテレータを変換し新しいイテレータを返します — 遅延なので、map().filter().map() のチェーンは中間コレクションをゼロ生成します。map は各要素に適用。filter は述語が true を返す要素を保持。take(n) は n 要素後に停止(無限イテレータに有用)。skip(n) は最初の n 個を破棄。flat_map はネストされたイテレータをマップして平坦化。enumerate は各要素にインデックスをペア。消費者(collect、sum、for ループ)がイテレータを駆動するまで何も実行されません。

rust
let nums = vec![1, 2, 3, 4, 5, 6];

// map: transform each element
let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect();

// filter: keep elements matching predicate
let evens: Vec<_> = nums.iter().filter(|&&x| x % 2 == 0).collect();

// take / skip: limit or skip elements
let first3: Vec<_> = nums.iter().take(3).collect();     // [1,2,3]
let after2: Vec<_> = nums.iter().skip(2).collect();     // [3,4,5,6]

// flat_map: map then flatten one level
let words: Vec<_> = ["a b", "c"].iter()
    .flat_map(|s| s.split(' '))
    .collect();  // ["a","b","c"]

// enumerate: add index
for (i, v) in nums.iter().enumerate() {
    println!("{}: {}", i, v);
}

コンシューマメソッド

コンシューマメソッドが遅延イテレータチェーンを実際に実行に駆動します。collect() は FromIterator を実装する任意のコレクション(Vec、HashMap、String など)に結果を集めます。sum/product/count/fold はイテレータを単一値に還元します。find/any/all は短絡 — 答えが分かるとすぐに停止するため無限イテレータで効率的です。min/max は Option を返します(空イテレータで None)。遅延アダプタ + 最終コンシューマの組み合わせにより、イテレータチェーンは最適化後は手書きループと同等に効率的です。

rust
let nums = vec![1, 2, 3, 4, 5];

// collect: gather into a collection
let v: Vec<i32> = nums.iter().copied().collect();
let s: std::collections::HashSet<i32> = nums.iter().copied().collect();

// sum, product, count
let total: i32 = nums.iter().sum();        // 15
let prod: i32 = nums.iter().product();     // 120
let count = nums.iter().count();           // 5

// reduce / fold: accumulate into a single value
let max = nums.iter().copied().reduce(i32::max);  // Some(5)
let sum = nums.iter().fold(0, |acc, &x| acc + x); // 15

// find / any / all: short-circuiting
let first_even = nums.iter().find(|&&x| x % 2 == 0);  // Some(2)
let has_neg = nums.iter().any(|&x| *x < 0);  // false
let all_pos = nums.iter().all(|&x| *x > 0);  // true

// min / max
nums.iter().copied().min();  // Some(1)
nums.iter().copied().max();  // Some(5)

カスタムイテレータ

カスタムイテレータを作成するには、next() メソッドで Iterator トレイトを実装します。一度行えば、70以上のアダプタとコンシューマメソッドを無料で取得します。イテレータは自身の状態(現在位置など)を追跡し、枯渇時に None を返す必要があります。コレクション型に IntoIterator を実装すると for ループ構文が有効になります。双方向またはランダムアクセスには、DoubleEndedIterator または ExactSizeIterator も実装してください。これが Vec、HashMap、Range、全標準コレクションが反復を提供する方法です。

rust
// Build a custom iterator for a counter
struct Counter {
    current: usize,
    max: usize,
}

impl Counter {
    fn new(max: usize) -> Self {
        Counter { current: 0, max }
    }
}

impl Iterator for Counter {
    type Item = usize;
    fn next(&mut self) -> Option<Self::Item> {
        if self.current < self.max {
            let val = self.current;
            self.current += 1;
            Some(val)
        } else {
            None
        }
    }
}

// Now all iterator methods work!
let sum: usize = Counter::new(5).sum();  // 0+1+2+3+4 = 10
let doubled: Vec<_> = Counter::new(3).map(|x| x * 10).collect();

無限イテレータとチェーンイテレータ

Rust イテレータは無限にできます — (1..) は永遠に自然数を生成し、repeat(x) は無限に繰り返します。アダプタが遅延なため安全です:take(n) が消費を制限します。cycle() は有限イテレータを無限に繰り返します。chain() はイテレータを順次連結します。zip() は要素を位置的にペアにします(短い方で停止)。peekable() で消費せずに次の要素を覗き見できます — パーサーに有用。遅延設計により無限イテレータは消費までコストゼロで、コンパイラがチェーンをタイトなループに最適化します。

rust
use std::iter;

// Infinite iterators (use with take!)
let naturals = (1..).take(10);  // 1..10
let zeros = iter::repeat(0).take(5);  // [0,0,0,0,0]
let alternating = iter::repeat_with(|| rand::random::<u8>());

// cycle: repeat a finite iterator infinitely
let pattern = [1, 2, 3].iter().cycle().take(7);
// [1,2,3,1,2,3,1]

// chain: concatenate two iterators
let combined = [1, 2].iter().chain([3, 4].iter());
// [1,2,3,4]

// zip: pair elements from two iterators
let pairs: Vec<_> = [1, 2, 3].iter().zip(['a', 'b', 'c']).collect();
// [(1,'a'), (2,'b'), (3,'c')]

// peekable: look ahead without consuming
let mut iter = [1, 2, 3].iter().peekable();
if let Some(&&first) = iter.peek() { println!("{}", first); }
17

エラーハンドリングの深掘り

Result と Option コンビネータ

コンビネータでネストされた match 式なしに失敗する操作をチェーンできます。map は Ok 値を変換し、map_err はエラーを変換します。and_then は Result を返す操作をチェーンします(エラーの flatmap)。ok_or は Option→Result を変換します。unwrap_or/unwrap_or_else/unwrap_or_default はフォールバック値を提供します。これらは優雅に合成できます:parse().map().and_then().map_err() で各ステップが失敗するパイプラインを作成し、最初の失敗が短絡します。本番コードでは unwrap() よりコンビネータを優先してください。

rust
// Result combinators chain operations without match
fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse::<i32>().map(|n| n * 2)
}

// and_then: chain fallible operations
fn validate(n: i32) -> Result<i32, String> {
    if n > 0 { Ok(n) } else { Err("must be positive".into()) }
}
let result = "5".parse::<i32>().and_then(validate);  // Ok(5)

// map_err: transform the error type
let r = "abc".parse::<i32>()
    .map_err(|e| format!("Parse failed: {}", e));

// ok_or / ok_or_else: convert Option to Result
let opt: Option<i32> = None;
let val = opt.ok_or("missing value");  // Err("missing value")

// unwrap_or / unwrap_or_else / unwrap_or_default
let x: i32 = "abc".parse().unwrap_or(0);

カスタムエラー型

カスタムエラー型でドメイン固有の失敗を表現できます。重要なパターン:各基となるエラー型に From を実装し ? 演算子が自動変換するようにします。これにより std::io::Error、ParseIntError などで明示的な map_err なしに ? を使用できます。Display の実装でエラーをユーザーフレンドリーにし、Debug は開発者向けです。列挙型ベースのエラーは Rust で慣用的です — 網羅的(コンパイラが欠落ケースで警告)でゼロコスト(ヒープアロケーションなし)。これは thiserror を使用する前の基盤です。

rust
#[derive(Debug)]
enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
    NotFound(String),
    Unauthorized,
}

// Implement From for automatic conversion with ?
impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}
impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self { AppError::Parse(e) }
}

impl std::fmt::Display for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO error: {}", e),
            AppError::Parse(e) => write!(f, "Parse error: {}", e),
            AppError::NotFound(s) => write!(f, "Not found: {}", s),
            AppError::Unauthorized => write!(f, "Unauthorized"),
        }
    }
}

// Now ? works for io::Error and ParseIntError automatically
fn read_config() -> Result<i32, AppError> {
    let s = std::fs::read_to_string("config.txt")?;  // io → AppError
    let n: i32 = s.trim().parse()?;                   // parse → AppError
    Ok(n)
}

Error トレイトと Box<dyn Error>

std::error::Error はエラー型の標準ライブラリトレイトです(Debug + Display が必要)。Box<dyn Error> は最もシンプルなエラー型です — ? で任意のエラーを受け入れ、プロトタイピングや特定のエラーをプログラムで処理する必要のないアプリケーションに最適です。欠点:具象エラー型が失われるため、特定のバリアントでのマッチングには downcast_ref が必要です。ライブラリには具象列挙エラー型(thiserror 付き)を優先してください。アプリケーションには anyhow が Box<dyn Error> より良い選択肢です — バックトレースとエラーチェーンを保持するため。

rust
use std::error::Error;

// std::error::Error is the trait for all errors
// Requires: Debug + Display
fn do_something() -> Result<(), Box<dyn Error>> {
    let f = std::fs::read_to_string("file.txt")?;  // io::Error
    let n: i32 = f.parse()?;                         // ParseIntError
    println!("{}", n);
    Ok(())
}

// Box<dyn Error> is the quick-and-dirty error type
// — accepts any error via ?, but loses specific type info

// downcast to recover specific error type
match do_something() {
    Err(e) => {
        if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
            println!("IO: {}", io_err);
        }
    }
    _ => {}
}

thiserror クレート(ライブラリエラー)

thiserror はライブラリエラー型の標準クレートです。#[derive(Error)] マクロが Display(#[error("...")] から)と From(#[from] から)の実装を自動生成します。#[from] で ? が基となるエラーを列挙バリアントに変換します。これによりボイラープレートを排除しつつ、強く型付けされた網羅的なエラー列挙を保持します。ライブラリ(呼び出し元が特定のエラーでマッチする必要がある場合)には thiserror を使用してください。{0} プレースホルダは内部エラーの Display を挿入し、{id} のような名前付きフィールドは構造体フィールドを挿入します。

rust
use thiserror::Error;

// thiserror auto-generates Display and From impls
#[derive(Debug, Error)]
enum DataError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("parse failed: {0}")]
    Parse(#[from] std::num::ParseIntError),

    #[error("item {id} not found")]
    NotFound { id: u32 },

    #[error("invalid state: {msg}")]
    Invalid { msg: String },
}

// #[from] auto-implements From, so ? just works:
fn load() -> Result<i32, DataError> {
    let s = std::fs::read_to_string("data.txt")?;  // auto-converts
    let n: i32 = s.trim().parse()?;
    Ok(n)
}

anyhow クレート(アプリケーションエラー)

anyhow はアプリケーション/バイナリのエラーハンドリングの標準クレートです。anyhow::Error は std::error::Error を実装する任意のエラーをラップし、コンテキスト、バックトレース、エラーチェーンを追加します。context() で各失敗ステップに人間可読のメッセージを付加し、「Failed to read config: IO error: No such file」のようなチェーンを作成します。これによりデバッグがはるかに容易になります — どのステップが失敗したかとその理由が正確に分かります。main() とエラーを報告するだけでマッチしないアプリケーションコードには anyhow を使用し、呼び出し元が型付きエラーを必要とするライブラリには thiserror を使用してください。

rust
use anyhow::{Context, Result, anyhow};

// anyhow::Result = Result<T, anyhow::Error>
fn load_config() -> Result<String> {
    let content = std::fs::read_to_string("config.toml")
        .context("Failed to read config.toml")?;  // add context
    Ok(content)
}

fn main() -> Result<()> {
    let config = load_config()?;
    if config.is_empty() {
        // bail! / anyhow! create errors with format! syntax
        return Err(anyhow!("config is empty"));
    }
    println!("{}", config);
    Ok(())
}

// Error chain: "Failed to read config.toml: No such file..."
// anyhow preserves the full chain and backtrace
18

Cargo とクレートの深掘り

Cargo.toml 構造

Cargo.toml は Rust プロジェクトのマニフェストです。[package] がクレートメタデータを記述。[dependencies] が外部クレートをリスト — バージョン文字列は semver を使用(^1.0 は >=1.0、<2.0 を意味)。features でオプション機能を有効化(serde の derive 機能が #[derive(Serialize)] を有効化)。[dev-dependencies] はテスト/ベンチマークのみ。[features] が条件コンパイルフラグを定義。[profile.release] が最適化設定を制御。edition フィールド(2015/2018/2021)が言語機能を決定 — 常に最新を使用してください。

rust
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"
authors = ["You <[email protected]>"]
license = "MIT"
description = "A sample app"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", optional = true }
rand = "0.8"

[dev-dependencies]
criterion = "0.5"  # only for tests/benches

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

[[bin]]
name = "myapp"
path = "src/main.rs"

[profile.release]
opt-level = 3
lto = true
strip = true

依存関係とフィーチャーフラグ

フィーチャーフラグで条件コンパイルを有効にします。各依存関係はフィーチャーを公開できます(例:serde の derive)。default-features = false でデフォルトフィーチャーを削除しバイナリサイズを削減。オプショナル依存関係(optional = true)はフィーチャーが dep:name で有効化した場合のみコンパイルされます。フィーチャーは加算的です — オンにするもので、オフにはしません。これによりフィーチャー統一が保証されます:2つの依存関係が serde の異なるフィーチャーを有効化する場合、Cargo は全フィーチャーの和集合で serde を一度コンパイルします。#[cfg(feature = "x")] でコードを条件コンパイルします。

rust
# Cargo.toml
[dependencies]
# Version requirements: ^1.2 (compatible), =1.2.3 (exact), >=1.0,<2.0 (range)
serde = "1.0"                    # ^1.0 (default: compatible)
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", default-features = false }  # disable defaults

# Optional dependencies (enabled by a feature)
tokio = { version = "1", optional = true }

[features]
# "async" feature enables the optional tokio dep
async = ["dep:tokio"]
# Features can enable other features
full = ["async", "serde/derive"]

# In code:
# #[cfg(feature = "async")]
# fn run_async() { ... }

ワークスペース(マルチクレートプロジェクト)

ワークスペースは Cargo.lock とターゲットディレクトリを共有する複数の関連クレートをグループ化します。これによりビルドが高速化(共有コンパイルキャッシュ)し、全クレートが同じ依存関係バージョンを使用することが保証されます。メンバーは path = "../core" で互いに依存できます。[workspace.dependencies] でバージョン管理を一元化 — メンバークレートは { workspace = true } で参照します。resolver = "2"(edition 2021 のデフォルト)がターゲットごとのフィーチャー統一を使用し、一部のビルド問題を回避します。モノレポ、複数コンポーネントのライブラリ、または core/CLI/server を分割するプロジェクトにワークスペースを使用します。

rust
# Root Cargo.toml — workspace manifest
[workspace]
members = [
    "core",
    "cli",
    "server",
    "utils",
]
resolver = "2"

# Shared dependencies across all members
[workspace.dependencies]
serde = "1.0"
tokio = "1"

# In member crates (e.g., cli/Cargo.toml):
# [dependencies]
# serde = { workspace = true }
# core = { path = "../core" }  # local path dependency

ビルドプロファイルと最適化

プロファイルが cargo のビルド方法を制御します。dev(cargo build のデフォルト)はコンパイル速度を優先。release(cargo build --release)はランタイムパフォーマンスを優先。主要なノブ:opt-level(0-3、サイズは 's'、最小は 'z')、lto(クレート境界をまたぐリンク時最適化)、codegen-units(1 = 最高最適化だが最遅コンパイル)、strip(より小さいバイナリのためにシンボル削除)、panic = 'abort'(アンワインドを無効化、より小さいバイナリ)。本番には lto = true、codegen-units = 1、strip = true を使用します。カスタムプロファイルは既存のものを継承します。

rust
# Cargo.toml
[profile.dev]
opt-level = 0        # no optimization (fast compile)
debug = true         # include debug symbols
overflow-checks = true

[profile.release]
opt-level = 3        # max optimization
lto = "fat"          # link-time optimization across crates
codegen-units = 1    # single codegen unit (better opt, slower compile)
strip = true         # strip debug symbols from binary
panic = "abort"      # smaller binary, no unwinding

[profile.release.package."*"]
opt-level = 2  # optimize dependencies less than your code

# Custom profile
[profile.bench]
inherits = "release"
debug = true  # keep symbols for profiling

# Usage: cargo build --release --profile bench

公開とドキュメント

crates.io への公開は永続的です — バージョンは上書きや削除できません(ヤンクのみ可能で、新しい依存関係を防ぐ)。name、version、description、license、repository が設定されていることを確認してください。cargo package がマニフェストをバリデーションし公開内容を表示します。cargo doc が /// ドキュメントコメントから HTML ドキュメントを生成します — ドキュメントテスト(``` ブロック内のコード)は cargo test でコンパイル・実行されます。例付きの良いドキュメントコメントはドキュメントとテストの両方です。内部アイテムを隠すには #[doc(hidden)] を使用します。

rust
# Before publishing:
# 1. Login (one-time)
# $ cargo login <token>  (token from crates.io)

# 2. Check the package
# $ cargo package        # creates .crate file, checks metadata
# $ cargo publish --dry-run

# 3. Publish
# $ cargo publish        # uploads to crates.io (irreversible!)

# Required fields in Cargo.toml for publishing:
# name, version, description, license, repository

# Documentation:
# $ cargo doc            # generate docs for your crate + deps
# $ cargo doc --open     # generate and open in browser
# $ cargo doc --no-deps  # only your crate

# Doc tests run automatically:
/// Adds two numbers.
/// 
/// # Examples
/// ```
/// let result = mycrate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
19

トレイトオブジェクトと動的ディスパッチ

dyn Trait の基礎

トレイトオブジェクト(dyn Trait)でランタイムポリモーフィズムが可能です:単一の変数が同じトレイトを実装する異なる具象型を保持できます。コンパイラが型ごとに vtable(仮想メソッドテーブル)を生成し、メソッド呼び出しは vtable を経由します(動的ディスパッチ)。これにはわずかなランタイムコストがありますが異種コレクションが可能になります。具象型がコンパイル時に不明または変動する場合にトレイトオブジェクトを使用します。

rust
trait Draw {
    fn draw(&self);
}

struct Circle { radius: f64 }
struct Square { side: f64 }

impl Draw for Circle {
    fn draw(&self) { println!("Circle r={}", self.radius); }
}
impl Draw for Square {
    fn draw(&self) { println!("Square s={}", self.side); }
}

// Trait object: type-erased, dynamic dispatch
let shapes: Vec<Box<dyn Draw>> = vec![
    Box::new(Circle { radius: 1.0 }),
    Box::new(Square { side: 2.0 }),
];
for s in &shapes { s.draw(); }

// Function taking trait object
fn render(shape: &dyn Draw) { shape.draw(); }

オブジェクト安全性

トレイトがオブジェクトセーフ(dyn Trait として使用可能)なのは以下の場合のみ:Self を返すメソッドがない、値で Self を取るメソッドがない、ジェネリックメソッドがない、全メソッドがディスパッチ可能。Clone、Default、From はオブジェクトセーフではありません。回避策には Box<Self> 戻り値(box_clone パターン)、トレイトの分割、または列挙型での静的ディスパッチが含まれます。コンパイラがオブジェクト安全性違反を明確に報告します。

rust
// Object-safe trait (can be made into dyn)
trait Animal {
    fn sound(&self) -> String;
    fn name(&self) -> &str;
}

// NOT object-safe: returns Self
trait Clone {
    fn clone(&self) -> Self;  // Self is unknown for dyn
}

// NOT object-safe: takes Self by value
trait Add {
    fn add(&self, other: Self) -> Self;
}

// NOT object-safe: generic method
trait From {
    fn from<T>(t: T) -> Self;
}

// Fix: use where clauses or separate traits
trait AnimalSafe {
    fn sound(&self) -> String;
    fn box_clone(&self) -> Box<dyn AnimalSafe>;
}

静的 vs 動的ディスパッチ

静的ディスパッチ(トレイト境界付きジェネリクス)は単相化します:コンパイラが具象型ごとに特殊化されたバージョンを生成し、インライン化と最大パフォーマンスを可能にしますがバイナリサイズのコストがあります。動的ディスパッチ(dyn Trait)はランタイムで vtable ルックアップを使用し、より小さいバイナリだが遅い呼び出し(インライン化を妨げる)。パフォーマンスクリティカルなコードには静的ディスパッチを優先し、異種コレクションとプラグインシステムには動的ディスパッチを使用してください。

rust
// Static dispatch (monomorphization)
fn max<T: Ord>(a: T, b: T) -> T {
    if a > b { a } else { b }
}
// Compiler generates max_i32, max_f64, etc.

// Dynamic dispatch (vtable lookup)
fn max_dyn(a: &dyn Ord, b: &dyn Ord) -> bool {
    // a > b  // Cannot use operators on dyn
    false
}

// Trait bound (static)
fn process<T: Display>(item: &T) {
    println!("{}", item);
}

// impl Trait (static, syntactic sugar)
fn process2(item: &impl Display) {
    println!("{}", item);
}

// dyn Trait (dynamic)
fn process3(item: &dyn Display) {
    println!("{}", item);
}

ライフタイム付きトレイトオブジェクト

トレイトオブジェクトはライフタイム境界を持てます:Box<dyn Trait + 'a> はトレイトオブジェクト(とその背後の具象型)が少なくとも 'a 生きる必要があることを意味します。デフォルトで Box<dyn Trait> は 'static を意味します。参照を保持する可能性のあるトレイトオブジェクトを格納する場合、ライフタイムを明示的に追加します。+ 構文でトレイト境界とライフタイムを組み合わせます。これはプラグインシステムとイベントハンドラで一般的です。

rust
trait Parser {
    fn parse(&self, input: &str) -> &str;
}

// Trait object with lifetime
fn make_parser() -> Box<dyn Parser> {
    Box::new(MyParser)
}

// Trait object holding references
struct Runner<'a> {
    parsers: Vec<Box<dyn Parser + 'a>>,
}

impl<'a> Runner<'a> {
    fn add(&mut self, p: Box<dyn Parser + 'a>) {
        self.parsers.push(p);
    }
}

// dyn Trait defaults to 'static when no lifetime given
fn static_parser() -> Box<dyn Parser> {
    Box::new(MyParser)
}

ダウンキャストと Any

Any トレイトでランタイム型チェックとダウンキャストが可能です。Any は全 'static 型に自動実装されます。downcast_ref と downcast_mut が Option を返し、安全な型復元を可能にします。これはプラグインシステム、動的設定、異種コンテナに有用です。Any は控えめに使用してください — 型システムをバイパスします。既知の代替には列挙型を、型安全なポリモーフィズムにはジェネリクスを優先してください。

rust
use std::any::Any;

// Any enables runtime type identification
let x: Box<dyn Any> = Box::new(42_i32);

// Downcast to concrete type
if let Some(n) = x.downcast_ref::<i32>() {
    println!("Got i32: {}", n);
}

// Store heterogeneous values
let mut bag: Vec<Box<dyn Any>> = vec![
    Box::new(42_i32),
    Box::new("hello".to_string()),
    Box::new(3.14_f64),
];

for item in &bag {
    if let Some(s) = item.downcast_ref::<String>() {
        println!("String: {}", s);
    } else if let Some(n) = item.downcast_ref::<i32>() {
        println!("i32: {}", n);
    }
}
20

宣言型マクロ

macro_rules! の基礎

macro_rules! はパターンにマッチしてコードに展開する宣言型マクロを定義します。$( $x:expr ),* はカンマ区切りの式リストにマッチし、ゼロ回以上繰り返します。$() ... * ブロックが各マッチに対して繰り返されます。マクロは型チェック前にコンパイル時に展開されます。衛生的です:マクロが導入する識別子は周囲のコードと衝突しません。ボイラープレート削減(vec!、println!、format!)にマクロを使用します。

rust
macro_rules! vec_of {
    ( $( $x:expr ),* ) => {
        {
            let mut v = Vec::new();
            $(
                v.push($x);
            )*
            v
        }
    };
}

let nums = vec_of!(1, 2, 3, 4);
let strs = vec_of!("a", "b", "c");

// Multiple patterns
macro_rules! greet {
    () => { println!("Hello!") };
    ($name:expr) => { println!("Hello, {}!", $name) };
    ($name:expr, $greeting:expr) => {
        println!("{}, {}!", $greeting, $name)
    };
}
greet!();
greet!("Alice");
greet!("Bob", "Hi");

フラグメント型

マクロフラグメントには特定の型があります:expr(式)、stmt(文)、ty(型)、pat(パターン)、ident(識別子)、tt(トークンツリー、最も柔軟)、literal(リテラル)など。フラグメント型がマクロが受け入れるものと解析方法を決定します。tt が最も一般的 — 任意の有効なトークンシーケンス。より良いエラーメッセージのために可能な限り具体的な型を使用してください。パーサーは Most-Recently-Added-Ambiguity ルールに従います。

rust
macro_rules! items {
    // $x:expr - expression (1 + 2, foo())
    // $x:stmt - statement (let x = 5;)
    // $x:ty - type (Vec<i32>, &str)
    // $x:pat - pattern (Some(x), (a, b))
    // $x:path - path (std::vec::Vec, Module::Type)
    // $x:ident - identifier (foo, Bar)
    // $x:literal - literal (42, "hello")
    // $x:tt - token tree (anything)
    // $x:block - block ({ ... })
    // $x:item - item (fn, struct)

    ($e:expr, $t:ty, $i:ident) => {
        let $i: $t = $e;
    };
}

items!(42, i32, my_num);
items!("hi", &str, greeting);

反復パターン

マクロの反復:$(...)* はゼロ以上にマッチ、$(...)+ は1以上にマッチ、$(...)? はゼロまたは1にマッチ。カンマのようなセパレータはマッチ間に配置。ネストされた反復で多次元データ(行列、リストのリスト)を処理。$() ... * 展開ブロックが各マッチに対して繰り返されます。同じ反復内の複数変数は同じ回数マッチする必要があります。アキュムレータパターンには @prefix 内部ルールを使用します。

rust
macro_rules! sum {
    // Zero or more: $(...)*
    ( $( $x:expr ),* ) => {
        {
            let mut total = 0;
            $(
                total += $x;
            )*
            total
        }
    };

    // One or more: $(...)+
    ( first $(, $x:expr )+ ) => {
        println!("First and more");
    };

    // With separator and count
    ( $( $x:expr ),+ $(; $sep:expr )? ) => {
        println!("List with optional separator");
    };
}

// Nested repetition
macro_rules! matrix {
    ( $( [ $( $x:expr ),* ] ),* ) => {
        vec![ vec![ $( $x ),* ],* ]
    };
}

衛生性とエクスポート

マクロ衛生性が識別子の衝突を防ぎます:マクロが導入する変数は自身のスコープに存在し、呼び出し元の変数をキャプチャやシャドウしません。マクロが定義されたクレートのアイテムを参照するには $crate を使用し、再エクスポート後も動作することを保証します。@prefix 規約がユーザーが直接呼ぶべきでない内部ヘルパールールをマークします。#[macro_export] がマクロをクレートルートに公開します。

rust
// Export at crate root
#[macro_export]
macro_rules! log {
    ($($arg:tt)*) => {
        // Hygienic: this T does not collide with caller's T
        let _ts: std::time::Instant = std::time::Instant::now();
        eprintln!("[{}] {}", "LOG", format!($($arg)*));
    };
}

// Use $crate to refer to crate items (works after re-export)
#[macro_export]
macro_rules! make_error {
    ($msg:expr) => {
        $crate::Error::new($msg)
    };
}

// Internal helper rule (convention: @ prefix)
macro_rules! count {
    (@count $x:expr, $($rest:expr),*) => { 1 + count!(@count $($rest),*) };
    (@count $x:expr) => { 1 };
    () => { 0 };
}

一般的なマクロパターン

マクロは DSL 構築とボイラープレート削除に優れています。一般的なパターン:ビルダー DSL(html!、sql!)、テストアサーション(assert_approx!)、設定(config!)、コード生成(derive のようなマクロ)。マクロは衛生的でコンパイル時なのでランタイムコストがありません。制限:64 を超える再帰深さなし、複雑なエラーメッセージ、非自明な解析の困難さ。複雑なメタプログラミングには手続き型マクロを使用します。

rust
// 1. Builder DSL
macro_rules! html {
    ($tag:ident { $($body:tt)* }) => {
        format!("<{}>{}</{}>", stringify!($tag), html_inner!($($body)*), stringify!($tag))
    };
}

// 2. Test assertion
macro_rules! assert_approx {
    ($a:expr, $b:expr, $eps:expr) => {
        assert!(($a - $b).abs() < $eps, "{} != {} within {}", $a, $b, $eps);
    };
}

// 3. Configuration
macro_rules! config {
    ( $( $key:ident : $val:expr ),* ) => {
        {
            let mut m = std::collections::HashMap::new();
            $(
                m.insert(stringify!($key).to_string(), $val.to_string());
            )*
            m
        }
    };
}

let cfg = config! { host: "localhost", port: 8080 };
21

Cargo とワークスペース

ワークスペースのセットアップ

ワークスペースは依存関係とターゲットディレクトリを共有する複数クレートをグループ化します。メンバーは明示的またはグロブでリストされます。[workspace.package] が共有パッケージメタデータを定義し、.workspace = true で継承されます。[workspace.dependencies] が依存関係バージョンを一元化し、全クレートが同じバージョンを使用することを保証します。これがバージョン競合を防ぎビルドを高速化します(単一 Cargo.lock)。CLI + ライブラリ + サーバーのようなマルチクレートプロジェクトにワークスペースを使用します。

rust
# Root Cargo.toml
[workspace]
members = ["crates/*", "cli", "server"]
resolver = "2"

[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Team <[email protected]>"]
license = "MIT"

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

# Member crate: crates/mylib/Cargo.toml
[package]
name = "mylib"
version.workspace = true
edition.workspace = true

[dependencies]
serde.workspace = true
tokio.workspace = true

ビルドプロファイル

プロファイルがコンパイル設定を制御します。dev は高速コンパイルを優先(opt-level 0、デバッグシンボル)。release はランタイムパフォーマンスを最大化(opt-level 3、LTO、単一 codegen unit)。LTO(リンク時最適化)がクレート境界をまたぐインライン化を有効化。panic = "abort" はより小さいバイナリを生成するがアンワインドを無効化。パッケージごとのオーバーライド(profile.dev.package."*")で dev でも依存関係を最適化。カスタムプロファイルは既存のものを継承します。

rust
# Cargo.toml
[profile.dev]
opt-level = 0        # No optimization (fast compile)
debug = true         # Include debug symbols
overflow-checks = true

[profile.release]
opt-level = 3        # Max optimization
debug = false
lto = "fat"          # Link-time optimization
codegen-units = 1    # Single unit (better opt, slower compile)
panic = "abort"      # Smaller binary, no unwinding
strip = true         # Strip symbols

[profile.dev.package."*"]
opt-level = 2        # Optimize dependencies in dev

# Custom profile
[profile.bench]
inherits = "release"
debug = true

# Usage: cargo build --release, cargo build --profile=bench

フィーチャーと条件コンパイル

フィーチャーで条件コンパイルを有効にします。オプショナル依存関係は自動的にフィーチャーになります。cfg(feature = "...") でフィーチャーでコードをゲート。--no-default-features が渡されない限りデフォルトフィーチャーセットが有効。フィーチャーは加算的であるべきです(多くを有効に、少なくではなく)。バイナリサイズ削減、複数バックエンドのサポート、実験的コードのゲートにフィーチャーを使用します。条件 derive マクロには cfg_attr と組み合わせます。相互排他的なフィーチャーは避けてください。

rust
# Cargo.toml
[features]
default = ["json"]
json = ["serde_json"]
yaml = ["serde_yaml"]
async-runtime = ["tokio"]

[dependencies]
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
serde_yaml = { version = "0.9", optional = true }
tokio = { version = "1", optional = true, features = ["full"] }

# Code: conditional compilation
#[cfg(feature = "json")]
pub fn parse_json(s: &str) -> Result<Value, Error> {
    serde_json::from_str(s)
}

#[cfg(not(feature = "json"))]
pub fn parse_json(_: &str) -> Result<Value, Error> {
    Err(Error::FeatureNotEnabled)
}

ビルドスクリプト(build.rs)

build.rs はコンパイル前に実行され、コード生成、環境の埋め込み、C ライブラリのリンクを可能にします。cargo:rerun-if-* ディレクティブがスクリプトの再実行タイミングを制御。println! マクロでコードを生成し、include! でクレートに取り込みます。一般的な用途:バージョン情報の埋め込み、バインディングの生成(bindgen)、protobuf/SQL スキーマのコンパイル、システムライブラリのリンク。ビルドスクリプトは毎回のビルドで実行されるため高速に保ってください。

rust
// build.rs: runs before compilation
use std::env;
use std::fs;
use std::path::Path;

fn main() {
    // Tell Cargo to rerun if env changes
    println!("cargo:rerun-if-env-changed=DATABASE_URL");

    let out_dir = env::var("OUT_DIR").unwrap();
    let dest = Path::new(&out_dir).join("config.rs");

    let db_url = env::var("DATABASE_URL")
        .unwrap_or_else(|_| "sqlite://default.db".to_string());

    // Generate Rust code at build time
    fs::write(&dest, format!(
        "pub const DATABASE_URL: &str = \"{}\";",
        db_url
    )).unwrap();

    // Link a C library
    println!("cargo:rustc-link-lib=static=mylib");
    println!("cargo:rustc-link-search=native=/usr/local/lib");
}

// In code: include generated file
include!(concat!(env!("OUT_DIR"), "/config.rs"));

公開とドキュメント

cargo publish がクレートを crates.io にアップロードします。公開前に --dry-run で検証してください。cargo doc がドキュメントコメント(///)から HTML ドキュメントを生成します。ドキュメントコメントのコードブロックは cargo test --doc でテストされます。Examples、Panics、Errors セクションを含めてください。メタデータ(description、repository、keywords)が発見性を向上させます。一度公開すると、バージョンは再利用や削除できません — 新しいプロジェクトが依存するのを防ぐには yank を使用します。

rust
# Publish to crates.io
cargo login <token>     # One-time authentication
cargo publish           # Publish current crate

# Before publishing:
cargo publish --dry-run # Verify package contents
cargo package           # Inspect the .crate file

# Documentation
cargo doc               # Generate docs
cargo doc --open        # Generate and open
cargo doc --no-deps     # Only this crate

# In code: doc comments
/// Adds two numbers.
///
/// # Examples
/// ```
/// let result = mycrate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }

# README and metadata in Cargo.toml
[package]
description = "A short description"
repository = "https://github.com/user/repo"
readme = "README.md"
keywords = ["parser", "cli"]
categories = ["command-line-utilities"]
22

手続き型マクロ

マクロの型

手続き型マクロはコンパイル時に Rust コードを生成し、トークンストリーム上で動作します。3つの型:関数風(custom!())、derive(#[derive(Custom)])、属性(#[custom])。proc-macro = true の別クレートが必要です。syn クレートが Rust 構文を解析し、quote がコードを生成し、proc-macro2 がテストを可能にします。proc マクロは強力ですが複雑です — derive マクロ、DSL、宣言型マクロでは扱えないコード生成に使用します。

rust
// Three types of procedural macros:
// 1. Function-like: my_macro!(...)
// 2. Derive: #[derive(MyMacro)]
// 3. Attribute: #[my_macro]

// Cargo.toml for a proc-macro crate
// [lib]
// proc-macro = true

// [dependencies]
// syn = { version = "2", features = ["full"] }
// quote = "1"
// proc-macro2 = "1"

use proc_macro::TokenStream;

#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
    "fn answer() -> i32 { 42 }".parse().unwrap()
}

// Usage: make_answer!();
// Generates: fn answer() -> i32 { 42 }

Derive マクロ

Derive マクロは #[derive(MyMacro)] で注釈された型にトレイト実装を追加します。syn が入力を DeriveInput AST に解析します。quote! が変数の # 補間でコードを生成します。ヘルパー属性(attributes(hello))でフィールドやバリアントのカスタマイズを可能にします。一般的な derive マクロ:Debug、Clone、Serialize、Deserialize。生成されたコードはモジュールに追加されるため、元の型を変更できません。

rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let expanded = quote! {
        impl HelloMacro for #name {
            fn hello() {
                println!("Hello from {}!", stringify!(#name));
            }
        }
    };

    expanded.into()
}

// Usage:
// #[derive(HelloMacro)]
// struct Pancakes;
// Pancakes::hello();  // "Hello from Pancakes!"

// Helper attributes
#[proc_macro_derive(HelloMacro, attributes(hello))]
pub fn hello_with_attr(input: TokenStream) -> TokenStream { /* ... */ }

属性マクロ

属性マクロ(#[my_attr])は注釈するアイテムを変換し、完全に置き換えることも可能です。属性引数と注釈アイテムの両方を受け取ります。一般的な用途:ロギング、キャッシュ、非同期ラッパー(#[tokio::main])、ルーティング(#[get("/path")])。属性マクロはアイテムのシグネチャを変更、コードを追加、追加アイテムを生成できます。derive マクロより柔軟ですが正しく使用するのは難しいです。

rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn log_calls(attr: TokenStream, item: TokenStream) -> TokenStream {
    let attr_args = syn::parse_macro_input!(attr as syn::AttributeArgs);
    let input_fn = parse_macro_input!(item as ItemFn);

    let fn_name = &input_fn.sig.ident;
    let fn_block = &input_fn.block;

    let expanded = quote! {
        fn #fn_name() {
            println!("Calling {}", stringify!(#fn_name));
            let __result = (|| #fn_block)();
            println!("Finished {}", stringify!(#fn_name));
            __result
        }
    };

    expanded.into()
}

// Usage:
// #[log_calls]
// fn my_function() -> i32 { 42 }

関数風マクロ

関数風手続き型マクロ(my_macro!())は任意のトークンストリームを受け入れ、カスタム DSL を可能にします。Parse を実装して受け入れ構文を定義します。マクロは入力に基づきバリデーション、変換、コード生成ができます。一般的な用途:SQL クエリ(sqlx)、HTML テンプレート(maud)、設定 DSL。宣言型マクロとは異なり、proc マクロは複雑な構文を解析しコンパイル時に任意の計算を実行できます。遅いビルドを避けるため高速に保ってください。

rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::Parse, parse::ParseStream, parse_macro_input};

// Custom syntax: sql!(SELECT * FROM users WHERE id = $1)
struct SqlQuery {
    query: String,
}

impl Parse for SqlQuery {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let query = input.to_string();
        Ok(SqlQuery { query })
    }
}

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let SqlQuery { query } = parse_macro_input!(input as SqlQuery);

    let expanded = quote! {
        {
            static QUERY: &str = #query;
            // Compile-time SQL validation could go here
            QUERY
        }
    };

    expanded.into()
}

テストとデバッグ

proc マクロのテスト:trybuild がコンパイラ出力(成功またはエラーメッセージ)を期待ファイルと比較する UI テストを実行します。derive マクロの場合、生成されたコードがコンパイルされ正しく動作することをテストします。eprintln! でデバッグ(コンパイル中に出力)または cargo expand で(展開されたマクロ出力を表示)。マクロ開発は反復的です:マクロを書き、cargo expand で出力を検査し、問題を修正します。マクロの構文とサポート機能を明確にドキュメント化してください。

rust
// Cargo.toml
// [dev-dependencies]
// trybuild = "1"

// tests/ui/my_macro.rs - test file
// #[derive(MyMacro)]
// struct Foo;
// fn main() { Foo::hello(); }

// tests/ui/my_macro.stderr - expected error
// error: ...

// Test runner
#[test]
fn ui() {
    let t = trybuild::TestCases::new();
    t.pass("tests/ui/pass_*.rs");
    t.compile_fail("tests/ui/fail_*.rs");
}

// Debugging with eprintln
#[proc_macro_derive(Debug)]
pub fn debug_derive(input: TokenStream) -> TokenStream {
    eprintln!("Input tokens: {}", input);
    let ast = syn::parse2(input.clone().into()).unwrap();
    eprintln!("Parsed AST: {:#?}", ast);
    TokenStream::new()
}
23

マクロ

macro_rules!

macro_rules! が宣言型マクロを定義します。$x はキャプチャ、expr は式にマッチ。$(...)* は反復。マクロはコンパイル時に展開されます。ボイラープレート削減に有用です。標準の vec! マクロが同様に動作します。

rust
macro_rules! vec_of {
    ($($x:expr),*) => {{
        let mut v = Vec::new();
        $(v.push($x);)*
        v
    }};
}
let nums = vec_of!(1, 2, 3);

手続き型マクロ

手続き型マクロはコンパイル時にコードを生成します。3つの型:derive(#[derive(Debug)])、属性(#[my_attr])、関数風(my_macro!)。macro_rules! より強力ですが別クレートが必要です。serde、tokio、diesel で使用されます。

rust
// In a separate crate with proc-macro = true
use proc_macro::TokenStream;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // Generate impl HelloMacro for the type
    // Returns new TokenStream
}

一般的なマクロ

組み込みマクロ:println!/format! は出力、vec! はベクタ、assert!/assert_eq! はテスト、dbg! はデバッグ、todo!/unreachable! は制御フロー用。全て macro_rules! ベースです。dbg! はチェーン用に値を返します。

rust
println!("Hello, {}!", "world");
format!("x = {}", 42);
vec![1, 2, 3];
assert!(1 + 1 == 2);
assert_eq!(2 + 2, 4);
dbg!(some_variable);  // Debug print
todo!("Not implemented");
unreachable!();

マクロ衛生性

Rust マクロは衛生的です:マクロが導入する識別子は呼び出しスコープの識別子と衝突しません。これにより微妙なバグを防ぎます。マクロ内の temp は外側の temp とは異なります。宣言型マクロは常に衛生的です。

rust
macro_rules! swap {
    ($a:expr, $b:expr) => {
        let temp = $a;
        $a = $b;
        $b = temp;
    };
}
// temp is hygienic: does not conflict with outer temp
let mut temp = 1;
let mut x = 2;
swap!(temp, x);  // Works correctly

反復

マクロの反復:$(...)* はゼロ以上にマッチ、$(...)+ は1以上にマッチ。セパレータ(カンマ)を指定できます。$x が各値をキャプチャ。可変長引数風関数に有用です。標準の println! が複数引数にこれを使用します。

rust
macro_rules! sum {
    ($($x:expr),*) => {
        0 $(+ $x)*
    };
}
let total = sum!(1, 2, 3, 4);  // 10
// $(...)* zero or more, $(...)+ one or more
// $(...),? optional trailing comma
24

Async の深掘り

async/await

async fn は Future を返します。.await は future が準備できるまで中断します。Future は遅延です:await されるまで何も実行されません。コンパイラが async fn をステートマシンに変換します。実行には tokio または async-std ランタイムを使用してください。

rust
async fn fetch_data() -> String {
    // Simulate async work
    String::from("data")
}
async fn process() {
    let data = fetch_data().await;
    println!("{}", data);
}

Tokio ランタイム

tokio::main が非同期 main を有効にします。spawn がタスクを作成(グリーンスレッドのようなもの)。join! が複数の future を並行待機。Tokio は I/O、タイマー、スケジューリングを提供します。Rust で最も人気のある非同期ランタイムです。

rust
#[tokio::main]
async fn main() {
    let task1 = tokio::spawn(async { work1().await });
    let task2 = tokio::spawn(async { work2().await });
    let (r1, r2) = tokio::join!(task1, task2);
}

チャネル(非同期)

mpsc(マルチプロデューサ、シングルコンシューマ)チャネルで非同期通信が可能です。send/recv は非同期です。チャネルはバッファを持ちます(32 メッセージ)。全送信者がドロップすると recv は None を返します。プロデューサー-コンシューマーパターンに有用です。

rust
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32);
    tokio::spawn(async move {
        tx.send("hello").await.unwrap();
    });
    while let Some(msg) = rx.recv().await {
        println!("{}", msg);
    }
}

Select

select! は複数の future の最初の完了を待機します。他の future はドロップされます。タイムアウトと操作の競争に有用です。ネットワークサーバーで一般的なパターンです。各ブランチはガードパターンを持てます。

rust
tokio::select! {
    result = task1 => {
        println!("Task1 done: {:?}", result);
    }
    result = task2 => {
        println!("Task2 done: {:?}", result);
    }
    _ = tokio::time::sleep(Duration::from_secs(5)) => {
        println!("Timeout");
    }
}

Stream

Stream は Iterator の非同期等価物です。next().await で次のアイテムを取得。StreamExt が map、filter、for_each を提供。ネットワークやファイルからのデータチャンク処理に有用です。async-stream クレートがストリーム作成を簡素化します。

rust
use tokio_stream::{self as stream, StreamExt};
let mut stream = stream::iter(vec![1, 2, 3]);
while let Some(item) = stream.next().await {
    println!("{}", item);
}
// Map, filter like iterators but async
stream.map(|x| x * 2).filter(|x| *x > 2).for_each(|x| async move {
    println!("{}", x);
}).await;
25

Cargo とクレート

Cargo.toml

Cargo.toml はマニフェストファイルです。[package] がメタデータを定義。[dependencies] が外部クレートをリスト。features でオプション機能を有効化。[dev-dependencies] はテストのみ。Edition 2021 が最新の安定版です。バージョンは semver を使用します。

rust
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"

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

[dev-dependencies]
pretty_assertions = "1"

Cargo コマンド

cargo new がバイナリプロジェクトを作成(ライブラリには --lib)。build が target/ にコンパイル。--release が最適化を有効化。check は build より高速(コード生成なし)。clippy が一般的な間違いをキャッチ。fmt がコードをフォーマット。doc が HTML ドキュメントを生成。add が依存関係を挿入。

rust
cargo new myapp        # Create new project
cargo build            # Compile
cargo build --release  # Optimized build
cargo run              # Build and run
cargo test             # Run tests
cargo check            # Fast type-check
cargo fmt              # Format code
cargo clippy           # Lint
cargo doc --open       # Generate docs
cargo add serde        # Add dependency

ワークスペース

ワークスペースはターゲットディレクトリと Cargo.lock を共有する複数クレートをグループ化します。メンバーは個別クレートです。[workspace.dependencies] が依存関係バージョンを一元化。各クレートは workspace = true で参照します。共有コンパイルによりビルドが高速化。rust-analyzer のような大規模プロジェクトで使用されます。

rust
# Root Cargo.toml
[workspace]
members = ["crate-a", "crate-b"]

# Shared dependencies
[workspace.dependencies]
serde = "1.0"

# In crate-a/Cargo.toml
[dependencies]
serde = { workspace = true }

フィーチャー

フィーチャーで条件コンパイルを有効にします。--no-default-features が渡されない限りデフォルトフィーチャーが有効。cfg(feature = ...) でコードをゲート。依存関係の dep: 構文がフィーチャー統一を回避。オプション機能とプラットフォーム固有コードに有用です。クレートはコンシューマーにフィーチャーを公開できます。

rust
# Cargo.toml
[features]
default = ["csv"]
csv = ["dep:csv-parse"]
json = ["dep:serde_json"]

# Conditional compilation
#[cfg(feature = "csv")]
pub fn parse_csv() { /* ... */ }

公開

crates.io は Rust のパッケージレジストリです。cargo login で認証。--dry-run で問題をキャッチ。一度公開するとバージョンは再公開できません(yank は検索から非表示にするのみ)。semver に従ってください:修正は patch、機能は minor、破壊的変更は major。README と license が必要です。

rust
# Login (one time)
cargo login <token>

# Check before publishing
cargo publish --dry-run

# Publish to crates.io
cargo publish

# Version bumping
cargo bump patch  # 0.1.0 -> 0.1.1
cargo bump minor  # 0.1.0 -> 0.2.0
cargo bump major  # 0.1.0 -> 1.0.0
26

Rust のテスト

ユニットテスト

テストは #[cfg(test)] モジュール内にあります。use super::* で親をインポート。#[test] がテスト関数をマーク。assert_eq! で等価性をチェック。#[should_panic] でパニックを期待。cargo test で実行。ユニットテストはコードと同じ場所にあります。cfg(test) 属性がリリースビルドでテストがコンパイルされないことを保証します。

rust
pub fn add(a: i32, b: i32) -> i32 { a + b }

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
    #[test]
    #[should_panic]
    fn test_panic() {
        panic!("expected");
    }
}

統合テスト

統合テストは tests/ ディレクトリにあります。各ファイルが別クレートとしてコンパイルされます。公開 API のみテストできます。エンドツーエンドテストに有用です。--test <name> で特定テストを実行。統合テストはコンパイルが遅いですが実際のインターフェースをテストします。

rust
// tests/integration_test.rs
use myapp::add;

#[test]
fn test_add_integration() {
    assert_eq!(add(2, 3), 5);
}
// Run: cargo test --test integration_test
// Each file in tests/ is a separate crate

テストの組織化

#[ignore] は --ignored が渡されない限りテストをスキップ。名前パターンでテストをフィルタ。--nocapture で println! 出力を表示。テストはデフォルトで並行実行。逐次実行には --test-threads=1 を使用。カスタムハーネスがデフォルトのテストランナーを置き換えできます。ベンチマークとプロパティテストに有用です。

rust
#[test]
fn it_works() { /* ... */ }

// Custom test harness
#[test]
#[ignore = "slow test"]
fn slow_test() { /* ... */ }

// Run only ignored tests
cargo test -- --ignored

// Filter by name
cargo test test_add

// Show output
cargo test -- --nocapture

アサーション

assert! はブール値をチェック。assert_eq!/assert_ne! は失敗時にデバッグ出力で値を比較。カスタムメッセージがデバッグに役立ちます。浮動小数点には approx クレートを使用。部分等価には PartialEq を実装。アサーション失敗時に両方の値を表示するデバッグ出力です。

rust
assert!(true);                          // Boolean
assert_eq!(2 + 2, 4);                   // Equality
assert_ne!(3, 4);                       // Inequality
assert!(x > 0, "x must be positive");   // Custom message
assert_eq!(a, b, "got {}, expected {}", a, b);
// Debug output on failure
assert_eq!(vec![1, 2], vec![1, 2]);

プロパティテスト

proptest がランダム入力を生成して失敗ケースを見つけます。Strategies(範囲内の a)が入力ジェネレータを定義。prop_assert! が最小の反例で失敗を報告。シュリンキングが最小の失敗入力を見つけます。エッジケースで手書きテストより優れています。Haskell の QuickCheck に似ています。

rust
// Cargo.toml: proptest = "1"
use proptest::prelude::*;

proptest! {
    #[test]
    fn test_add_commutative(a in -1000..1000, b in -1000..1000) {
        prop_assert_eq!(add(a, b), add(b, a));
    }
    #[test]
    fn test_string_len(s in ".{0,100}") {
        prop_assert!(s.len() <= 100);
    }
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.