mcpField
R
rust-advanced

rust-advanced

github.com/trancong12102開発748 回インストール5 週間前に更新
リポジトリ

rust-advanced について

本番向け Rust 開発のためのスキルです。所有権、エラー処理、非同期処理、型設計、パフォーマンスの規約と、安全性や一般的な落とし穴を解説します。

主な機能

  1. 01所有権とスマートポインターの選択
  2. 02ライブラリとアプリのエラー処理規約
  3. 03Tokio 非同期処理とキャンセル安全性
  4. 04型システムとライフタイムのパターン
  5. 05unsafe と FFI の安全規則
  6. 06性能分析と最適化の判断指針

活用シーン

  1. 01共有状態と借用方針の設計
  2. 02ライブラリやアプリのエラー型の選定
  3. 03非同期タスクの Send とキャンセル問題の調査
  4. 04ボトルネックの評価と最適化手法の選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/trancong12102/agentskills --skill rust-advanced

インストール後、Claude Code で/rust-advancedを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
async-rust.md
error-handling.md
macros.md
ownership.md
performance.md
traits.md
type-patterns.md
unsafe-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 境界カスタム enumC 互換のエラーコードにマッピングする必要があります
必須のパターン
  1. アプリケーションコードで ? を使って伝播させるときは、必ずコンテキストを追加します:

    fs::read_to_string(path)
        .with_context(|| format!("failed to read config: {path}"))?;
    
  2. ライブラリのエラー 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 },
    }
    
  3. 短いチェーンでは、入れ子の match よりも Result のコンビネーターを優先します: map、map_err、and_then、unwrap_or_else。

  4. ライブラリコードで 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 のポリシー

  1. スコープを最小限にする — 必要最小限の行だけを unsafe {} で囲みます。
  2. すべての unsafe ブロックに、不変条件がどのように保たれるかを説明する // SAFETY: コメントを必ず付けます。
  3. 安全な抽象化を優先する — transmute よりも as キャスト、bytemuck::cast、from_raw_parts を使用します。transmute は最後の手段としてのみ、ターボフィッシュ構文を使ってください。
  4. FFI 境界のルール: bindgen でバインディングを生成し、薄い安全な Rust API でラップして、すべての不変条件を文書化します。
  5. 借用チェッカーを回避するために unsafe を使ってはいけません。 その必要があると思った場合は、データ構造を再設計してください。

よくある落とし穴

  1. .await をまたいで MutexGuard を保持する — future が !Send になり、 tokio::spawn が失敗します。待機する前にブロック内でロックのスコープを閉じてください。

  2. RefCell の二重借用によるパニック — 借用が残っていると borrow_mut() は パニックになります。借用の有効期間を完全に制御できない場合は、try_borrow_mut() を使用してください。

  3. Mutex のデッドロック — Rust の Mutex は再入可能ではありません。同じ ミューテックスを同じスレッドで二度ロックしないでください。複数のロックは一貫した順序で取得してください。

  4. collect::<Vec<Result<T, E>>>() と collect::<Result<Vec<T>, E>>() の違い — 2 つ目の形式は最初のエラーで処理を中断し、ほとんどの場合はこちらが適切です。

  5. &str ではなく &String を受け入れる — &String は &str に自動 デリファレンスされますが、その逆はできません。関数シグネチャでは常に &str を受け入れてください。

  6. ライブラリコードで unwrap() を使う — 呼び出し元がクラッシュします。適切な エラー型と ? を使うか、文書化された不変条件とともに expect() を使用してください。

  7. Result を返す関数に #[must_use] を付け忘れる — 呼び出し元が エラーを黙って無視する可能性があります。コンパイラーは警告しますが、カスタム型にはこの属性が必要です。

  8. 非同期コードで std::sync::Mutex を使う — エグゼキューターのスレッドをブロックします。 非同期コンテキストでは tokio::sync::Mutex を使用してください。

  9. ホットループ内で String::from を使う — 反復のたびにメモリーを確保します。String::with_capacity() で事前に 確保するか、Cow<str> を使用してください。

  10. select! でキャンセル安全性を無視する — 勝者にならなかった future は ドロップされます。キャンセル安全でない操作では、データが黙って失われます。

  11. 最初に clone() を使う — 多くの場合、借用チェッカーと格闘している兆候です。 まず所有権の構成を見直すか、参照を使用してください。

  12. 適切なエラー列挙型ではなく Box<dyn Error> を使う — 特定のバリアントに 対してマッチできなくなります。構造化されたエラーには thiserror を使用してください。


リファレンスファイル

特定のトピックに取り組む際は、関連するリファレンスファイルを読んでください。

ファイル読むタイミング
references/ownership.md内部可変性、スマートポインター、Cow、Pin、ライフタイムのテクニック
references/traits.mdトレイトオブジェクト、シールドトレイト、包括実装、HRTB、分散
references/error-handling.mdthiserror v2、anyhow、Result コンビネーター、エラー設計
references/async-rust.mdTokio ランタイム、キャンセル、JoinSet、Send/Sync、select!
references/performance.mdゼロコスト、SIMD、アリーナ割り当て、rayon、キャッシュ最適化
references/unsafe-ffi.mdunsafe の強力な機能、bindgen による FFI、transmute、生ポインター
references/macros.md宣言的マクロ、手続き型マクロ、derive マクロ、syn/quote
references/type-patterns.mdNewtype、typestate、PhantomData、const ジェネリクス、ビルダー

コメント