idiomatic-rust について
Rust の慣用的な表現に焦点を当てたコーディングスキルです。イテレーター、変換、derive、新しい型、命名の選択肢を扱い、Rust コードを整える開発者向けに標準的な検証項目も示します。
主な機能
- 01式を中心とする Rust のパターン
- 02イテレーターパイプラインの指針
- 03変換 Trait の選択
- 04derive と新しい型の指針
- 05命名と検証の助言
活用シーン
- 01ループをイテレーターパイプラインに整理
- 02型に適した変換 Trait を選択
- 03derive で定型コードを簡素化
- 04Rust の命名とコードスタイルを確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rewrite-rs/ skills --skill idiomatic-rustインストール後、Claude Code で/idiomatic-rustを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
BOILERPLATE.mdNAMING.mdagents/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 を有効にしていない限り、その指摘は違反ではなく提案として扱います。