rust-advanced について
本番向け Rust 開発のためのスキルです。所有権、エラー処理、非同期処理、型設計、パフォーマンスの規約と、安全性や一般的な落とし穴を解説します。
主な機能
- 01所有権とスマートポインターの選択
- 02ライブラリとアプリのエラー処理規約
- 03Tokio 非同期処理とキャンセル安全性
- 04型システムとライフタイムのパターン
- 05unsafe と FFI の安全規則
- 06性能分析と最適化の判断指針
活用シーン
- 01共有状態と借用方針の設計
- 02ライブラリやアプリのエラー型の選定
- 03非同期タスクの Send とキャンセル問題の調査
- 04ボトルネックの評価と最適化手法の選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill rust-advancedインストール後、Claude Code で/rust-advancedを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/async-rust.mderror-handling.mdmacros.mdownership.mdperformance.mdtraits.mdtype-patterns.mdunsafe-ffi.md詳細
Rust 上級編:パターン、規約、落とし穴
このスキルでは、本番環境向け Rust アプリケーションの構築に関するルール、規約、アーキテクチャ上の決定事項を定めています。よくある落とし穴を防ぎ、拡張性の高いパターンを徹底するため、意図的に特定の方針を採用しています。
ここで言及するクレートの詳しい API ドキュメントについては、適切な別のツール(ドキュメント検索、ウェブ検索など)を使用してください。このスキルでは、API 全体ではなく、これらのパターンをどのように使うか、またなぜ使うかに焦点を当てています。
所有権と借用のルール
内部可変性 — 判断フローチャート
Need shared mutation?
YES → Single-threaded or multi-threaded?
Single-threaded → Is T: Copy?
YES → Cell<T> (zero overhead, no borrow tracking)
NO → RefCell<T> (runtime borrow checking, panics on violation)
Multi-threaded → High contention?
NO → Arc<Mutex<T>> (simple, correct)
YES → Arc<RwLock<T>> (many readers, few writers)
or lock-free types (crossbeam, atomic)
NO → Use normal ownership / borrowing
スマートポインターの選び方
| 型 | 使用する場面 |
|---|---|
Box<T> | 再帰型、スタック上の大きな値、トレイトオブジェクト |
Rc<T> | 単一スレッドで共有する所有権(ツリー、グラフ) |
Arc<T> | マルチスレッドで共有する所有権 |
Cow<'a, T> | 借用する場合と所有する場合があるとき — 不要なクローンを避けます |
Pin<Box<T>> | 自己参照型、非同期フューチャー |
Cow のルール
関数が入力を変更する場合と、そのまま変更せずに渡す場合があるときは、Cow<str> または Cow<[T]> を受け取ります。変更が不要な場合に割り当てが発生するのを避けられます。所有権が必要になることがない関数の引数では、&str を優先してください。
エラー処理の方針
基本原則:ライブラリでは thiserror、アプリケーションでは anyhow を使います
| コンテキスト | クレート | 理由 |
|---|---|---|
| ライブラリクレート | thiserror | 呼び出し側が特定のエラーバリアントを照合する必要があります |
| バイナリ/アプリケーション | anyhow | エラーはコンテキスト付きでユーザー向けメッセージに伝播します |
| 内部モジュール | thiserror | 親モジュールが処理できる型安全なエラーバリアントを使えます |
| FFI 境界 | カスタム enum | C 互換のエラーコードにマッピングする必要があります |
必須のパターン
-
アプリケーションコードで
?を使って伝播させるときは、必ずコンテキストを追加します:fs::read_to_string(path) .with_context(|| format!("failed to read config: {path}"))?; -
ライブラリのエラー enum では、自動変換に
#[from]を使います:#[derive(thiserror::Error, Debug)] pub enum DbError { #[error("connection failed: {0}")] Connection(#[from] std::io::Error), #[error("query failed: {reason}")] Query { reason: String }, } -
短いチェーンでは、入れ子の
matchよりもResultのコンビネーターを優先します:map、map_err、and_then、unwrap_or_else。 -
ライブラリコードで
unwrap()を使ってはいけません。 不変条件が文書化され、確実に守られる場合に限り、expect()を使ってください。
トレイトシステムの規約
トレイトオブジェクトとジェネリクス — 判断基準
Need runtime polymorphism (heterogeneous collection, plugin system)?
YES → dyn Trait (Box<dyn Trait> or &dyn Trait)
NO → impl Trait / generics (zero-cost, monomorphized)
主なパターン
- ジェネリクスよりも関連型:型ごとに自然な実装がちょうど 1 つある場合に使います(例:
Iterator::Item)。 - シールドトレイト:下流のクレートが独自にトレイトを実装するのを防ぐ必要がある場合に使います。semver の安定性に不可欠です。
- ブランケット実装:境界条件を満たすすべての型に機能を追加する場合に使います(例:
impl<T: Display> ToString for T)。 - スーパートレイト:あるトレイトが論理的に別のトレイトの保証を必要とする場合に使います(例:
trait Printable: Debug + Display)。
オブジェクト安全性のルール
トレイトがオブジェクト安全(dyn Trait として使用可能)であるのは、次の条件を満たす場合に限ります:
Selfを返すメソッドがない- ジェネリック型パラメーターを持つメソッドがない
- すべてのメソッドが
self、&self、または&mut selfを取る
dyn Trait + async が必要な場合は、#[async_trait] を使うか、手動で Box<dyn Future> を返してください。トレイト内のネイティブ async は、まだオブジェクト安全ではありません。
Rust 非同期処理のルール
ランタイム:Tokio をデフォルトにします
#[tokio::main] と #[tokio::test] を使って tokio を使用します。非同期コンテキスト内で CPU 負荷の高い処理を行う場合は、tokio::task::spawn_blocking または rayon を使います。
ネイティブ async トレイト — 可能な限り #[async_trait] を外します
Rust 1.75 以降では、トレイト内の async fn がネイティブに動作します。非同期メソッドを持つ dyn Trait が必要でない限り、ネイティブ構文を使ってください。
Send/Sync ルール
tokio::spawn に渡す Future は Send でなければなりません。Send ではない Future の主な原因は、.await の時点をまたいで MutexGuard(またはその他の !Send 型)を保持することです。
修正方法: await する前にガードを破棄するか、ブロック内でロックのスコープを限定します。
{
let mut guard = lock.lock().unwrap();
guard.push(42);
} // guard dropped
do_async_thing().await; // future is Send
キャンセル安全性 — 非同期処理で最も危険な落とし穴
Future はどの .await の時点でも破棄される可能性があります(特に tokio::select! 内)。どの操作がキャンセル安全なのかを把握しておきましょう。
| 操作 | キャンセル安全? |
|---|---|
mpsc::Receiver::recv | はい |
AsyncReadExt::read | はい |
AsyncWriteExt::write_all | いいえ |
AsyncBufReadExt::read_line | いいえ |
キャンセル安全ではないコードには、tokio::spawn でラップする(JoinHandle を破棄しても、起動したタスクはキャンセルされません)か、協調的なキャンセルのために tokio_util::sync::CancellationToken を使用します。
構造化並行性:JoinSet を使用する
let mut set = tokio::task::JoinSet::new();
for url in urls {
set.spawn(fetch(url));
}
while let Some(result) = set.join_next().await {
result??;
}
型システムのパターン
ニュータイプ — ゼロコストのドメイン型
プリミティブ型をラップして、個別の型を作成します。UserId と OrderId の混同を防げます。
struct UserId(u64);
struct OrderId(u64);
// fn process(user: UserId, order: OrderId) — compiler prevents swaps
Typestate — コンパイル時のステートマシン
ライフサイクルの状態を型パラメーターとして表現します。無効な状態遷移はコンパイルエラーになります。
struct Connection<S> { socket: TcpStream, _state: PhantomData<S> }
struct Disconnected;
struct Connected;
impl Connection<Disconnected> {
fn connect(self) -> Result<Connection<Connected>> { ... }
}
impl Connection<Connected> {
fn send(&self, data: &[u8]) -> Result<()> { ... }
// send() is unavailable on Connection<Disconnected>
}
Const ジェネリクス — 型パラメーターとしての配列サイズ
struct Matrix<const ROWS: usize, const COLS: usize> {
data: [[f64; COLS]; ROWS],
}
impl<const N: usize> Matrix<N, N> {
fn trace(&self) -> f64 { (0..N).map(|i| self.data[i][i]).sum() }
}
PhantomData の分散
| マーカー | 分散 | 用途 |
|---|---|---|
PhantomData<T> | 共変 | T を概念上「所有」する |
PhantomData<fn(T)> | 反変 | T を消費する(まれ) |
PhantomData<fn(T) -> T> | 不変 | 正確な型である必要がある |
PhantomData<*const T> | 不変 | 生ポインターの意味論 |
パフォーマンス判断のフレームワーク
Is this a hot path (profiled, not guessed)?
NO → Write clear, idiomatic code. Don't optimize.
YES → Which bottleneck?
CPU-bound computation → rayon::par_iter() for data parallelism
Many small allocations → Arena allocator (bumpalo)
Iterator chain not vectorizing → Check for stateful dependencies,
use fold/try_fold, or restructure as plain slice iteration
Cache misses → #[repr(C)] + align, struct-of-arrays layout
Heap allocation → Box<[T]> instead of Vec<T> when size is fixed,
stack allocation for small types, SmallVec for usually-small vecs
ゼロコストのルール
イテレーターチェーン(filter().map().sum())は、手書きのループと同じコードにコンパイルされるため、読みやすさを優先して使用しましょう。ただし、状態を持つイテレーターチェーンは自動ベクトル化を妨げることがあります。SIMD の詳細は references/performance.md を参照してください。
Unsafe のポリシー
- スコープを最小限にする — 必要最小限の行だけを
unsafe {}で囲みます。 - すべての
unsafeブロックに、不変条件がどのように保たれるかを説明する// SAFETY:コメントを必ず付けます。 - 安全な抽象化を優先する —
transmuteよりもasキャスト、bytemuck::cast、from_raw_partsを使用します。transmuteは最後の手段としてのみ、ターボフィッシュ構文を使ってください。 - FFI 境界のルール:
bindgenでバインディングを生成し、薄い安全な Rust API でラップして、すべての不変条件を文書化します。 - 借用チェッカーを回避するために
unsafeを使ってはいけません。 その必要があると思った場合は、データ構造を再設計してください。
よくある落とし穴
-
.awaitをまたいでMutexGuardを保持する — future が!Sendになり、tokio::spawnが失敗します。待機する前にブロック内でロックのスコープを閉じてください。 -
RefCellの二重借用によるパニック — 借用が残っているとborrow_mut()は パニックになります。借用の有効期間を完全に制御できない場合は、try_borrow_mut()を使用してください。 -
Mutexのデッドロック — Rust のMutexは再入可能ではありません。同じ ミューテックスを同じスレッドで二度ロックしないでください。複数のロックは一貫した順序で取得してください。 -
collect::<Vec<Result<T, E>>>()とcollect::<Result<Vec<T>, E>>()の違い — 2 つ目の形式は最初のエラーで処理を中断し、ほとんどの場合はこちらが適切です。 -
&strではなく&Stringを受け入れる —&Stringは&strに自動 デリファレンスされますが、その逆はできません。関数シグネチャでは常に&strを受け入れてください。 -
ライブラリコードで
unwrap()を使う — 呼び出し元がクラッシュします。適切な エラー型と?を使うか、文書化された不変条件とともにexpect()を使用してください。 -
Resultを返す関数に#[must_use]を付け忘れる — 呼び出し元が エラーを黙って無視する可能性があります。コンパイラーは警告しますが、カスタム型にはこの属性が必要です。 -
非同期コードで
std::sync::Mutexを使う — エグゼキューターのスレッドをブロックします。 非同期コンテキストではtokio::sync::Mutexを使用してください。 -
ホットループ内で
String::fromを使う — 反復のたびにメモリーを確保します。String::with_capacity()で事前に 確保するか、Cow<str>を使用してください。 -
select!でキャンセル安全性を無視する — 勝者にならなかった future は ドロップされます。キャンセル安全でない操作では、データが黙って失われます。 -
最初に
clone()を使う — 多くの場合、借用チェッカーと格闘している兆候です。 まず所有権の構成を見直すか、参照を使用してください。 -
適切なエラー列挙型ではなく
Box<dyn Error>を使う — 特定のバリアントに 対してマッチできなくなります。構造化されたエラーにはthiserrorを使用してください。
リファレンスファイル
特定のトピックに取り組む際は、関連するリファレンスファイルを読んでください。
| ファイル | 読むタイミング |
|---|---|
references/ownership.md | 内部可変性、スマートポインター、Cow、Pin、ライフタイムのテクニック |
references/traits.md | トレイトオブジェクト、シールドトレイト、包括実装、HRTB、分散 |
references/error-handling.md | thiserror v2、anyhow、Result コンビネーター、エラー設計 |
references/async-rust.md | Tokio ランタイム、キャンセル、JoinSet、Send/Sync、select! |
references/performance.md | ゼロコスト、SIMD、アリーナ割り当て、rayon、キャッシュ最適化 |
references/unsafe-ffi.md | unsafe の強力な機能、bindgen による FFI、transmute、生ポインター |
references/macros.md | 宣言的マクロ、手続き型マクロ、derive マクロ、syn/quote |
references/type-patterns.md | Newtype、typestate、PhantomData、const ジェネリクス、ビルダー |