mcpField
I
idiomatic-rust

idiomatic-rust

github.com/rewrite-rs開発1042 回インストール10 週間前に更新
リポジトリ

idiomatic-rust について

Rust の慣用的な表現に焦点を当てたコーディングスキルです。イテレーター、変換、derive、新しい型、命名の選択肢を扱い、Rust コードを整える開発者向けに標準的な検証項目も示します。

主な機能

  1. 01式を中心とする Rust のパターン
  2. 02イテレーターパイプラインの指針
  3. 03変換 Trait の選択
  4. 04derive と新しい型の指針
  5. 05命名と検証の助言

活用シーン

  1. 01ループをイテレーターパイプラインに整理
  2. 02型に適した変換 Trait を選択
  3. 03derive で定型コードを簡素化
  4. 04Rust の命名とコードスタイルを確認

インストール

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

$npx skills add https://github.com/rewrite-rs/skills --skill idiomatic-rust

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

同梱ファイル

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

BOILERPLATE.md
NAMING.md
agents/
openai.yaml

詳細

慣用的な Rust

このスキルのテーマは表現です。Rust を読む人が期待するのは、どのような形式でしょうか?

慣用的な Rust の形

文より式を優先します。値を返す match は、すべてのアームで let mut に代入するものより適しています。また、答えを生成するチェーンは、ループ内でフラグ変数を設定して後で確認する方法より適しています。ランタイムチェックではなく、型システムに不変条件を担わせましょう。let ... else を使うと、ネストした if let の下で正常系の処理が右へずれていくことなく、早期リターンを先頭に置けます。パターンの真偽値判定には matches! を使います。ツールチェーンが対応していれば、if-let チェーンで条件を組み合わせられます。これは新しめのエディションで使えるため、まずリポジトリの MSRV を確認してください。

イテレーター

インデックスを使ったループより、まずイテレーターパイプラインを検討します。for i in 0..v.len() { ... v[i] ... } には、ほぼ必ずイテレーターを使った同等の書き方があります。イテレーター版なら、不正な境界でランタイムにパニックするのではなく、コンパイルに失敗します。いったん Vec にしてから変換するのではなく、目的の型に直接収集しましょう。

// Reads like a translation.
let mut names = Vec::new();
for user in &users {
    names.push(user.name.clone());
}

// Reads like Rust.
let names: Vec<String> = users.iter().map(|u| u.name.clone()).collect();

collect は HashMap、HashSet、String、Result<Vec<_>, _> にも収集できます。最後の型は、イテレーションと ? を組み合わせる方法です。

use std::num::ParseIntError;

fn parse_all(lines: &[&str]) -> Result<Vec<i64>, ParseIntError> {
    lines.iter().map(|line| line.parse::<i64>()).collect::<Result<Vec<_>, _>>()
}

本当の副作用がある場合や、コンビネーターでは分かりにくくなる早期終了がある場合は、素直な for ループを優先してください。イテレーターを使うことより、分かりやすさが大切です。

変換

ある型から別の型を構築できる場合は、From を実装してください。ブランケット実装によって Into も自動的に利用できるため、両方向を手書きで実装する必要はありません。変換が失敗する可能性がある場合は、From とパニックやセンチネル値の組み合わせではなく、TryFrom を実装してください。

struct Celsius(f64);
struct Fahrenheit(f64);

impl From<Celsius> for Fahrenheit {
    fn from(c: Celsius) -> Self {
        Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
    }
}

手書きの実装より derive を優先する

手書きする前に、#[derive(Debug, Clone, PartialEq, Default, Hash)] を検討してください。derive による実装は実際のフィールドから生成されるため、構造体の変更に合わせて常に同期されます。derive では不適切な場合にのみ、手動で実装してください。

  • フィールドごとのデフォルト値では不変条件が崩れる場合に、それを維持する Default(各フィールドがゼロではなく、合計が 100 になる Percentage など)。
  • 秘密のフィールド(API キーやパスワードなど)を出力せず、伏せる必要がある Debug。
  • 論理的に等しい値でも、等価性の判定に含めるべきでないフィールド(キャッシュされたハッシュやタイムスタンプなど)の値が異なる場合の PartialEq。

列挙型の場合も同じルールです。意図するバリアントに #[default] を付けた #[derive(Default)] を使います。このディレクトリの BOILERPLATE.md に、これらの対応例があります。

ニュータイプ

ニュータイプはランタイムコストがなく、引数を取り違えた呼び出しを型エラーにできます。単なる数値ではなく、ドメイン上の概念(ID や通貨額など)を表すために u64、String、f64 をそのまま使っている箇所では、ニュータイプを検討してください。

命名

名前の単語には、それぞれ意味が必要です。BookingService ではなく Bookings を使いましょう。Service、Manager、Helper、Util、Data、Info は、名前を最後まで考えきれていないことを示します。as_/to_/into_ という接頭辞は、コストを伴うことを示します。つまり、無料のビュー、割り当て、所有権の消費です。フィールドアクセサーに get_ は付けません。頭字語は単語として扱います(HttpClient、Uuid)。Self を必要としないヘルパーは、名前空間として使う impl ブロックではなく、フリー関数にします。段階別の表と具体例のペアは NAMING.md にあります。

設計上の兆候を示すボイラープレート

繰り返しがすべて derive で解消できるわけではありません。よく似た match アームが 5 つある場合や、転送メソッドが 10 個あるラッパーがある場合は、抽象化が欠けている兆候かもしれません。Deref の落とし穴を含む具体例のペアについては、BOILERPLATE.md を参照してください。

対象外

このスキルが扱うのは表現であり、判断ではありません。クローンと借用の使い分けは /ownership-not-clone、エラー型の形は /rust-errors、不正な状態を表現できないようにする方法は /type-driven-design が担当します。

検証

動作は同じで、形だけが違います。標準の 3 つを実行してください。

cargo clippy --all-targets   # add -- -D warnings only if the repo has no lint config
cargo fmt --check
cargo test

リポジトリで設定されている lint レベルで 3 つを実行してください。リポジトリが clippy::pedantic を有効にしていない限り、その指摘は違反ではなく提案として扱います。

コメント