Rust Best Practices Guide
Rustの最新のベストプラクティスを網羅したガイドです。コードスタイル、エラーハンドリング、パフォーマンス最適化、並行処理、プロジェクト構成、依存関係管理、ドキュメンテーション、テスト、セキュリティ、およびCI(継続的インテグレーション)について、実践的な知識を習得できます。
description の原文を見る
A comprehensive guide to modern Rust best practices covering style, error handling, performance, concurrency, project organization, dependency management, documentation, testing, security, and CI.
SKILL.md 本文
指示
一般的なコーディング規約とスタイル
-
標準的な命名規約に従う: すべての識別子について Rust の命名慣例に従う。型名(構造体、列挙型、トレイト)と列挙型のバリアントは
UpperCamelCaseを使用する。関数、メソッド、モジュール、変数名はsnake_caseを使用する。定数と静的変数はSCREAMING_SNAKE_CASEを使用する。例えば、構造体はUserAccount、関数はprocess_requestという名前になる。CamelCase でのすべて大文字の頭字語は避け(UUIDの代わりにUuidを使用)、短縮形より完全な単語を優先する。目的の名前が予約キーワードの場合は、生識別子を使用する(例:r#trait)か、末尾にアンダースコアを追加する。 -
rustfmt によるコード整形: 公式の Rust スタイルガイド(主に
rustfmtで実装)に準拠する。インデントに 4 スペースを使用し、行の幅を 100 文字以下に保つ。rustfmtがコードを自動的に整形して一貫したスタイルを実施し、コードレビューでの議論を避ける。CI では、書式チェック(cargo fmt -- --check)を含めて、書式が正しくないコードを拒否する。これにより、プロジェクト全体で統一されたスタイルが保証され、読みやすくなり、コミュニティの一貫性に貢献する。 -
慣用的な表現: Rust の慣用句を活用して、明確かつ簡潔に。可能な限り中間変数より式を優先する(例:
let x = if cond { a } else { b };を使用し、可変のlet x;に if/else ブロックを続けない)。高レベルのコンストラクトで十分な場合、低レベルのループや手動メモリ管理よりもイテレータと所有権システムを活用して明確なコードを書く。コメントは完全な文で書き、行コメント(//)を優先する。公開アイテムには///ドックコメントを使用し、例と使用ガイダンスを含める(ドキュメンテーションセクションを参照)。 -
モジュールの明確性を保つ: 明確な目的でモジュールを階層的に整理する。
src/の各ファイルはモジュールを定義できます。サブモジュール(独自のファイルまたはmod.rsを持つ)を使用して、コードを論理的にグループ化する。異常なケースを除き、#[path]を使用してファイルをインクルードしない。代わりに Cargo の規約に従う(例:キャンドルルートとしてsrc/lib.rs、サブモジュールはモジュール名の後に付けたファイル)。これにより、プロジェクト構造が予測可能になる。大規模なモジュールを小さなサブモジュールに分割して、ファイルを管理可能なサイズに保ち、関心事を分離する。 -
プロジェクト構造の一貫性: Cargo の慣用的なレイアウトを使用する。パッケージはライブラリクレート(
src/lib.rs)や/またはバイナリクレート(プライマリバイナリの場合src/main.rs、追加バイナリはsrc/bin/)を含むことができます。ほとんどのコードをライブラリクレートに保つことで、テストと再利用が可能になり、main.rsはライブラリコードをインスタンス化して呼び出すだけにします。複数クレートのプロジェクトの場合、Cargo ワークスペースを検討して、単一のCargo.lockを共有し、バージョンを調整する。各クレートには明確な責務があるべき。例えば、ウェブアプリケーションでは、コアロジック、HTTP サーバーランタイム、ユーティリティ用の個別のクレートを持つかもしれません。
エラーハンドリングのベストプラクティス
-
回復可能なエラーには
Resultを優先: Rust は例外を使用しません。代わりに、回復可能なエラーの標準はResult<T, E>型です。失敗する可能性のある関数はResultを返し、呼び出し元がエラーを処理するか伝播するかを選択できます。panic!は回復不可能なエラーまたはインバリアント違反(バグを示す状況)にのみ使用します。例えば、ファイルを読み込む関数は、ファイルが見つからない場合にパニックするのではなく、Result<Contents, IOError>を返すべき。エラーをコールスタック上で伝播したい場合は、?演算子を使用して便利に呼び出し元に返す。 -
panic!とResultに関するガイドライン: 妥当な回復方法がなく、実行を続けることが無効またはアンセーフな状況でのみpanic!を呼び出す。これには、内部ロジックエラー、到達不可能なコードパス、または回復不可能な状態の破損が含まれます。特にライブラリコードは、予想されるエラー条件でのパニックを避け、代わりにエラーを返して、ライブラリユーザーが処理方法を決定できるようにします。バイナリ(アプリケーション)では、パニックはプログラムをクラッシュさせるため、真に回復不可能な状況のために予約します。expect/unwrapはテスト、プロトタイプ、またはクイックスクリプトのmainでのみ使用し、それでもexpectを使用して有用なメッセージを提供することを優先します。 -
エラー型とコンテキストを使用: 列挙型を使用して、ライブラリ用に明確なエラー型を定義します(各バリアントが異なるエラーケースを表す)。
thiserrorクレートは、カスタムエラー型のstd::error::Errorトレイト実装を簡単に導出できます。アプリケーションコード(バイナリ)の場合は、シンプルさのためにanyhowのような便利なエラーラッパーを使用します。anyhow::Errorは、任意のエラーを表現できる動的エラー型であり、バックトレースをキャプチャします。これはきめ細かいエラー処理が必要ないmainまたはハイレベルのコードで有用です。例えば、CLI ツールのmain関数はfn main() -> Result<(), anyhow::Error>として、異なるエラー型で自由に?を使用できます。anyhowで、.context("high-level description")または.with_context(|| ...)を使用してエラーにコンテキストを付加し、低レベルのエラーにより多くの意味を持たせます。これは、エラーが発生したときにプログラムが何をしていたかを明確にすることで、デバッグに役立ちます。ライブラリでは、結合化されたエラー(独自のError型またはよく知られたエラー型)を返すことを優先し、呼び出し元が応じられるようにします。これはanyhowを使用することに対する利点で、エラーの内部を隠しません。 -
サイレント失敗を回避: エラーを無視しない。
Resultが未使用の場合、Rust は警告します。?で伝播するか、OkとErrの両方のケースを明示的に処理することで処理する。特定のエラーが本当に実行不可能な場合は、最低でもログするか、なぜ安全に無視できるのかを文書化することが望ましい。unwrap_or_elseやif let Err(e) = ...でログするなどのメソッドは、そのまれなケースで有用です。 -
パニック安全性と
Drop:Drop実装内またはロックを保有している間にパニックするコードを書く際に留意します。これは意図しない結果につながる可能性があります(ミューテックスの中毒やリソース解放なしでの中止など)。リソースを保有している間にパニックを優先しない。そのような状況でResultを使用して失敗を示す。Rust のアンワインドは Drop を実行しますが、デストラクタ自体がパニックした場合、プロセスは中止します。
パフォーマンス最適化テクニック
-
ゼロコスト抽象化: Rust のゼロコスト抽象化を信頼する。低レベルのコードと比較して、パフォーマンスペナルティなしでハイレベルのコードを書くことができます。例えば、イテレータ、クロージャ、ジェネリクスは、手書きのループを超えるオーバーヘッドなしで、コンパイル時に最適化されます。常に明確で慣用的な Rust を優先します。コンパイラの最適化(LLVM)は非常に強力です。証拠のない、過度に複雑またはマイクロ最適化されたコードを書くことは避ける。ハイレベルのイテレータは多くの場合、明示的なループと同じマシンコードにコンパイルされるため、明確性と保守性のために使用します。
-
メモリ管理: 所有権と借用を活用してメモリを管理する。可能な限り、スタック上にデータを割り当てるか、連続構造(
Vecなど)で割り当て、メモリをフラグメント化する連結構造を使用することは避けます。ほとんどの使用ケースでは、VecまたはスライスがLinkedListよりもキャッシュに優しく、高速になります。データの不要なクローンを避ける。参照またはボローイング(&T)を使用してデータをコピーなしで渡す。高コストなクローン操作がある場合は、Rc/Arc(共有所有)やCow(コピーオンライト)などのスマートポインタを使用して最適化を検討します。ただし、Rc/RefCellには注意が必要です。スレッド間で共有する場合は、Rcがスレッドセーフでないため、Arc<Mutex<T>>を使用します。ホットパスで割り当てのオーバーヘッドが見つかった場合は、プーリングまたは割り当ての再利用を使用します(例:Vec::with_capacityで事前割り当て、またはbytesのような特定のケースでバッファを管理するライブラリを使用)。Rust は細かいメモリレイアウト制御を提供します。smallvecやarrayvecなどのクレートを使用して、特定のケースでヒープ割り当てを回避できます。 -
インライン化とコンパイラヒント: コンパイラは、より高い最適化レベルで、特に小さい関数またはジェネリクスで多くの関数を自動的にインライン化します。パフォーマンスクリティカルなスポット(例:非常にホットな小さな関数)で
#[inline]または#[inline(always)]を使用して、インライン化を示唆します。インライン化は関数呼び出しのオーバーヘッドを排除し、呼び出し境界の向こうのさらなる最適化を有効にします。ただし、影響を測定します。インライン化は時々バイナリサイズを増やすか、コードを膨張させるか、他の最適化を防ぐなど、パフォーマンスに害を与える可能性があります。コンパイラがそれらを不必要にインライン化している場合は、めったに使用されない重い関数で#[inline(never)]を使用します。また、コールドコードパス(例:エラーハンドリング)を#[cold]でマークして、ホットパスを良くして最適化できます。インライン化を調整した後、常にベンチマークして、それが役立つか確認してください。 -
ベンチマーキング: 適切なベンチマークとプロファイリングを使用して、最適化を指導する。Criterion クレートは、組み込みの
#[bench]がナイトリーを必要とするため、堅牢なマイクロベンチマークを書くための一般的な選択肢です。perf、Intel VTune、Windows のパフォーマンスアナライザーなどのツールを使用してコードをプロファイリングし、真のボトルネックを見つけます。cargo flamegraphまたは同様を使用してホットスポットを可視化することを検討します。アルゴリズムの複雑さを最初に最適化し(例:良い複雑さを持つ適切なアルゴリズムとデータ構造を使用)、その後、必要に応じて内部ループをマイクロ最適化します。メモリ使用状況とアクセスパターンに注意してください。メモリバウンドのコードは、算術の細かい調整よりも、キャッシュに優しい構造から利益を得ることができます。 -
ゼロコスト FFI と SIMD: 外部 C/C++ コードを呼び出す必要がある場合、FFI で呼び出しますが、Rust の安全性を尊重します(
unsafeと正しいextern宣言を使用)。FFI 呼び出しのオーバーヘッドは通常最小限です(関数呼び出しに似ている)ため、抽象化の意味でゼロコストですが、タイトなループで FFI 境界を横断することに注意してください。CPU 集約的なタスクでは、Rust の安定した SIMD サポート(std::arch内)またはpacked_simdなどのクレートを使用することを検討してください。ただし、SIMD がアドレスできるボトルネックを明確に識別した後のみです。高レベルの Rayon(データ並列処理)またはスレッドは、明示的な SIMD を書くよりも多くのケースで適用しやすいかもしれません。
並行処理と非同期プログラミング
-
所有権を通じた恐れのない並行処理: Rust の所有権モデルは、デフォルトでスレッドセーフを確保します。他のスレッドに送信しても安全なタイプは
Sendトレイトを実装し、スレッド間で参照を共有しても安全なタイプはSyncを実装します。ほとんどのプリミティブ型と標準コレクションはSendとSyncです(その内容がそうである限り)。RcまたはRefCell(スレッドセーフでない)のような型を使用する場合、コンパイラはそれらをスレッドに送信することを防ぎます。共有データに対してスレッドセーフな代替品(Arc、Mutex、RwLock)を優先します。可能な限り共有状態を最小化するように常に設計します。頻繁にロックする代わりに、メッセージパッシングまたはチャネル(例:std::sync::mpscまたはcrossbeam-channelなどのクレート)を優先して、データを転送します。これにより、競合と可能なデッドロックが減ります。 -
スレッドと同期: シンプルなマルチスレッド処理に対して、標準ライブラリからハイレベルの並行処理コンストラクトを使用します。
std::thread::spawnでスレッドを並列タスク用にスポーン、JoinHandleを使用してそれらを結合します。共有データをMutex<T>(相互排除の場合)またはRwLock<T>(複数の読み手が許可される場合)で保護します。より複雑な待機条件にはCondvarを使用します。多くのケースでは、複雑なロックより、メッセージパッシングが望ましい。チャネルは安全なキューを提供し、1 つのスレッドはデータを送信でき、別のスレッドは受け取ることができます。Rust のデータレース安全性を念頭に置いてください。Send/Syncを正しく使用してコードが コンパイルされると、実行時にデータレースがないことが保証されます。これは大きな勝利です。ただし、ロジックレース(ハイレベルロジックの競合状態)は回避すべきです。 -
非同期プログラミング(async/await): 高い並行性ネットワーキングまたは I/O バウンドなタスクの場合、async/await の使用を検討します。Rust の Async は協調的な並行処理です。Tokio または async-std のような非同期ランタイムは、多くのタスクを少数のスレッドで実行し、フューチャーをポーリングすることで。Tokio はエコシステムで最も広く使用されている非同期ランタイムで、豊富なエコシステム(タイマー、ネットワーキングなど)とデフォルトではマルチスレッドスケジューラーを提供しています。async-std は、Node.js に触発された同様の API を提供し、特定の使用ケースの代替案ですが、Tokio は本番システムの事実上の標準になっています。非同期コードを書くときは、
#[tokio::main](または同等)を使用してランタイムを開始し、async fnフューチャーで.awaitを使用します。スポーンするタスクがSendであることを確認します(Tokio のマルチスレッドスケジューラーはデフォルトでスポーンされたフューチャーがSendであることが必要です)。!Send フューチャーがある場合(例えば、RcをArcの代わりに使用)、current-thread ランタイムで実行するか、tokio::task::LocalSetを使用する必要があります(Tokio はそのような場合のためにspawn_localも提供します)。非同期コードの並行性は、複数のタスクが実行されて.awaitポイントで制御を譲るから来ています。 -
非同期並行処理に関する考慮事項: 非同期タスクはスレッドプール上で実行されますが、非同期関数内で長いブロッキング操作を実行してはいけません。これはエクゼキューター全体のスレッドをブロックします。任意のブロッキング I/O または CPU バウンドのワークについて、他の非同期タスクをスタルすることがないように、専用スレッドプール(Tokio は
spawn_blockingを提供)にオフロードします。非同期用に設計された同期プリミティブを使用します。例えば、stdMutexの代わりにtokio::sync::Mutex(リアクターのブロッキングを防ぐため)、またはtokio::sync::mpscを非同期チャネルのために使用します。非同期コードは、共有リソースを更新する 2 つのタスクがあるなど、同期されていない場合、ロジックにまだ競合状態を持つことができます。調整するためにArc<tokio::sync::Mutex<_>>またはチャネルを使用します。また、キャンセルに注意します。待機されたフューチャーは、呼び出し元がそれをドロップした場合、キャンセルされるかもしれません。ハーフダンの操作を処理する必要がある場合は、クリーンアップコードまたはdrop_guardパターンを書いてください。ハイレベルライブラリを活用します(Tokio のselect!マクロ、またはfuturesクレートユーティリティなど)複数の同時実行タスクとタイムアウトを管理するために。 -
Send と Sync マーカートレイト: 並行処理に関連して
SendとSyncトレイトを理解します。Sendは値を別のスレッドに安全に転送できることを意味し、Syncは値への参照をスレッド間で共有しても安全であることを意味します。Rust はSend/Syncパーツで構成されるタイプについては自動的に実装します。アンセーフコードを書くまたは FFI とやり取りする場合、カスタムタイプが正しく実装する(またはしない)ことを確認して、スレッドセーフを保持します。タイプが絶対にスレッドセーフであるかどうか確実でない限り、SendまたはSyncを手動で実装しないでください。これはアンセーフです。通常は、コンパイラの自動実装に頼り、タイプに含まれるT: Sendなどを示す必要がある場合はstd::marker::PhantomDataを使用します。
プロジェクト構造とモジュール整理
-
クレートとパッケージ: クレートは Rust の コンパイルユニットで、パッケージ(Cargo パッケージ)は Cargo.toml を持つ 1 つ以上のクレートのセットです。関心事を明確に分離するか、再利用を有効にするために個別のクレートを使用します。例えば、プロジェクトはコアライブラリクレートとそのライブラリを使用するバイナリクレートを持つかもしれません。各クレートはキャンドルルートソースファイル(
src/lib.rsまたはsrc/main.rsなど)を持っています。デフォルトでは、cargo newは バイナリクレート(main.rs)を持つパッケージを作成します。src/lib.rsも追加する場合、これは同じ名前のライブラリクレートを定義します。ほとんどのコードについてこのライブラリを使用し、main.rsを最小限に保ってください。これはテストと再利用を促進します。 -
モジュールと可視性: モジュール(
mod)を使用してクレート内のコードを整理します。モジュールはスコープと可視性を制御できます。プログラムの主要コンポーネント(例:network、database、handlersなど、ライブラリのサブモジュール)についてハイレベルのモジュールで開始します。各モジュール内で関連する型と関数をグループ化します。デフォルトでは、モジュール内のアイテムはプライベート。pubキーワードを使用してモジュール境界でアイテムを公開し、クレートの公開 API(ライブラリの場合)またはその他のモジュール(バイナリの場合)の一部とします。ライブラリでは、きれいな公開 API を目指してください。Rust API ガイドラインは実装の詳細をプライベートに保つことや、一貫した最小限のインターフェースを公開することを推奨しています。 -
ファイル整理: ファイルシステムへのモジュールマッピングについて、Cargo 規約に従います。例えば、
lib.rsのmod parser;はsrcのparser.rsまたはparser/mod.rsからコードをロードします。サブモジュールを個別のファイル(モジュールが小さい場合)またはmod.rsを持つサブディレクトリ(またはニューインラインファイルの命名を使用、例えばparser/mod.rsはparser.rsプラスparser/のサブモジュールでもできます)として整理する。一貫性が鍵です。多くのプロジェクトは今、mod.rsファイルを避けスタイルをフラットに使用します(Rust 2018+ はfoo.rsのモジュールをサブモジュールと共にfoo/bar.rsで許可し、mod.rs なし)。1 つのスタイルを選択して固守してください。より大きなプロジェクトについて、共通 Cargo 設定と依存関係を共有する Cargo ワークスペースでクレートをグループ化します。ワークスペースの各クレートは独自のサブディレクトリにあり、ワークスペースCargo.tomlがそれらを集計します。 -
整理のためのモジュール可視性: 内部ヘルパー関数と構造体の可視性を管理するために
pub(crate)とpub(super)を効果的に使用します。これにより、実装の詳細をテストできます(同じモジュール内にテストを宣言することで、または#[cfg(test)] mod testsを使用)しながら、公開 API から隠し続けます。これは、ライブラリのより清潔な API 表面とロジックのカプセル化につながります。
依存関係管理と Cargo フィーチャー
-
Cargo.toml ベストプラクティス: Cargo セマンティクスを使用してバージョンで依存関係をピンすします(キャレット要件がデフォルト、例:
foo = "1.2.3"は1.xと互換性があることを意味する)。依存関係で semantic Versioning を尊重します。デフォルトでは、Cargo は最新の semver 互換バージョンを選びます。バイナリアプリケーションの場合、再現可能なビルドを確保するためにCargo.lockをチェックインすることが推奨されています(ロックファイルは正確なバージョンを固定)。ライブラリについて、歴史的にはCargo.lockを無視する慣行でしたが、ダウンストリームクレートが最新の互換バージョンを使用できるように。最近、ガイダンスは「プロジェクトに最適なことをしてください」に柔らかくなりました。ライブラリの CI テストの一貫性のためにロックファイルをコミットするかもしれませんが、ライブラリとして依存関係として使用されるときに無視されます。どちらの場合でも、cargo updateを定期的に実行し、最新のバージョンをテストして、どんな破損を早期にキャッチしてください(CI がこれを自動化するのを助けることができます)。 -
オプション依存関係のフィーチャーの使用: Cargo フィーチャーフラグを活用して、依存関係をオプションにし、条件付きコンパイルを制御します。Cargo.toml で
optional = trueとしてめったに使用されない依存関係をマークし、名前付きフィーチャーでグループ化します。例えば、クレートに JSON シリアル化機能がオプションがある場合、[dependencies] serde = { version = "1.0", optional = true }および[features] json = ["serde"]があるかもしれません。この方法で、ユーザーは"json"フィーチャーを有効にしてオプトインでき、デフォルトでは軽量クレートに保つ。フィーチャーを明確かつポジティブに命名(フィーチャーは機能を追加する)。ネガティブな命名を避ける(例えば「no-std」の代わりに「std」という名前のフィーチャーを使用し、デフォルトでオンにして、オフにできます)。フィーチャーは依存関係グラフ全体で統一されている(任意のクレートがクレートのフィーチャーを有効化した場合、そのプログラムのすべてのユーザーについても有効になります)ため、加法的で互換性のあるように設計します。README またはドキュメントでフィーチャーを文書化して、ユーザーがオプション機能を有効にする方法を知るようにします。 -
依存関係の膨張を回避: 各依存関係はコンパイル時とバイナリサイズを増やす可能性があります。軽量クレートと標準ライブラリを可能な限り優先します。新しい依存関係を追加する前に、本当に必要か、フィーチャーゲート処理できるかを検討します。依存関係ツリーに注意を払ってください(
cargo treeとcargo tree -e features)大きなまたは重複している依存関係を特定します。Cargo フィーチャーを使用して、依存関係の未使用部分をカットダウンします(いくつかの人気のあるクレートは、デフォルトの重い機能を無効化するフィーチャーを持ちます)。例えば、serdeを使用する場合、デフォルトフィーチャーを無効化し、導出マクロだけが必要な場合はserde/deriveだけを有効化できます。また、ビルドのみの依存関係と開発依存関係を観察します。開発依存関係を本番以外のニーズに限定します(それらはダウンストリームクレートに含まれることはありませんが、コンパイル時間に影響を与えます)。 -
バージョン競合の処理: Cargo のバージョン解決のおかげで、semver に依存して依存関係が動作し続けることに一般的に頼ることができます。特定のバージョンが必要な場合(例えば、セキュリティフィックスまたは API 変更)、不等号要件(
>=、=など)または直接gitやpath依存関係を一時的なオーバーライドのために使用できます。ただし、維持管理者/上流で動作して、リリースされたバージョンの使用を保つことを優先します。ライブラリが広く使用されている場合、最小サポート Rust バージョン(MSRV)のポリシーを検討し、Cargo.toml(rust-versionフィールド)とドキュメントに注記してください。これにより、依存関係のアップグレードが警告なしに新しいコンパイラを必要としなくなります。 -
マルチクレートプロジェクト用ワークスペース: プロジェクトに複数の相互依存クレート(例えば、ライブラリと複数のバイナリユーティリティ)が含まれる場合、Cargo ワークスペースを使用します。これにより、共有
Cargo.lockと出力ディレクトリが可能に、ビルドを高速化し、依存関係バージョンをクレート間で一貫性を保ちます。ルートにCargo.tomlを置いてリスト[workspace]メンバーを配置、メンバーの個々のCargo.lockファイルを削除します(ワークスペースはトップレベルで 1 つ使用)。ワークスペースは、すべてのクレート間でコマンドを実行しやすくするためにも(例えば、cargo fmtはすべてのメンバーで実行)、すべてのクレートを実行するため使用します。
ドキュメンテーションとテスト規約
- Rustdoc ドキュメント記述: すべての公開アイテム(公開構造体、列挙型、関数、モジュールなど)は、その目的、使用方法、任意の重要な詳細を説明する rustdoc コメント(
///)を持つべき。クレートルート(lib.rsまたはバイナリのmain.rs)で、モジュールレベルのドックコメントを含める、クレートの機能と使用例の概要を提供(ライブラリについては、これはユーザーが docs.rs で見る最初のページです)。主要機能についてすべてのドキュメントに例を含めることを目指してください。Rust API ガイドラインは、可能なことほぼすべての公開アイテムについて例を含めることを推奨しています。ドキュメントにドックテストコードブロックを使用します。トリプルバッククォート付きの任意のコードrustは、cargo testでテストされて、正確であり続けることを確保します。例:/// 長方形の面積を計算します。 /// /// # 例 /// ``` /// let rect = Rectangle::new(5, 10); /// assert_eq!(50, rect.area()); /// ```
このサンプルはコンパイルおよび実行されます(出力は ///# 行を使用してセットアップを隠す限り、必須ではありません)。ドックサンプルを unwrap() または expect() を使用するのではなく、失敗可能な呼び出しについて ? 演算子を優先するように記述して、適切なエラーハンドリングを奨励します。ドックサンプルが fn main() -> Result<(), Box<dyn std::error::Error>> { ... } ラッパーで完全なプログラムのようにコンパイルできるように書くことで、隠された #use ...; を使用することで、ドックに表示されませんが、? を使用できます。
-
パニック、エラー、安全性のドキュメント: 公開関数がパニックできる場合(例えば、内部的な unwrap を持つかまたは無効な入力でパニックします)、ドックコメントの「# Panics」セクションでこれをドキュメント化します。同様に、関数が Result を返すか、エラー条件があった場合、どの状況でエラーが返されるか説明する「# Errors」セクションを追加します。アンセーフ関数またはトレイト実装については、呼び出し元または実装者がアップホールドしなければならないインバリアントを説明する「# Safety」セクションを含めます。例えば、
unsafe fn do_io(ptr: *mut u8, len: usize)を持つ場合、ポインタが len バイトのために有効である必要があることをドキュメント化します。このドキュメントは、ユーザーが API を正しく使用する方法を知るための重要で、将来のメンテナー(自分自身を含む)が推論を覚えるためのものです。Clippy は、# Safety セクションなしで pub unsafe fn に警告する linter を持っています。これは良い慣行を有効にするためです。 -
テスト戦略: Rust テスト規約に従います。#[cfg(test)] mod tests モジュール追加によってコードと同じファイルで単体テストを書きます。単体テストは機能の小さな単位(個別の関数またはタイプ)に焦点を当てるべき。#[test] 関数を使用してテストを定義する。アサーション(assert_eq!、assert! など)を使用して動作を検証する。エラーをチェックする必要があるテストについて、テストが Result<()> を返し、便宜性のために ? を使用できます(Result が Err を返すどんなテストが失敗します)。
-
統合テスト: より大きなスコープのテスト、またはパブリック API の外部パースペクティブからテスト については、tests/ ディレクトリの統合テストを使用します。tests/*.rs ファイルを作成します。各ファイルはライブラリクレートに自動的に依存する個別のクレートとしてコンパイルされます。これでは、クレートをインポート(例:
use my_crate::*;)テストをこれは外部ユーザーのようにであるかのごとく書きます。統合テストは、公開 API が使用可能で、エンドツーエンドで動作することを確保します。これはバイナリアプリケーション(例:assert_cmd クレートを使用してバイナリを実行し、出力をチェックして)テストに特に有用です。cargo testを実行するのは、すべての単体テスト、統合テストをコンパイルして実行し、ドックテストも抽出して実行します。個別のファイル焦点異なるエリア(例えば、tests/api.rs は API エンドポイント、tests/cli.rs は CLI インターフェーステスト)で統合テストを個別のファイルに保ちます。 -
ドキュメンテーションテスト: 記されているように、doctests は自動的に実行されます。ドックのサンプルコードが実際に動作することを確認してください。
cargo testを実行します。時々、特定のドックテストをスキップまたは無視する必要があるかもしれません(例えば、失敗することを意図しているか、長時間実行)、/// ```rust,ignoreまたはno_runを追加することで(コンパイルされるがない実行)。これらをめったに使用しない。理想的には、ドックテストが可能なら実行すべきです。なぜなら、例とテスト二重として機能するからです。 -
テスト整理: テスト関数を明確に命名してカバーするものを示します。サブモジュールまたは単純にコード内の近接を使用して関連テストをグループ化できます。複数のテストについて共通セットアップが必要な場合、標準ライブラリのテストフィクスチャサポートを使用することを検討します(テスト関数内でセットアップを行うか、ヘルパー関数を抽出します。組み込み JUnit スタイルセットアップ/ティアダウンがありませんが、Rust のスコープルールでセットアップとリソースをドロップできます)。
-
継続的テスト: テストスイートをよく実行(例えば
cargo testまたはテストファイルの変化で実行するcargo watchのようなツール)。重要なコードの高いカバレッジを保つ。コードカバレッジツール(cargo tarpaulinまたはcargo LLVM coverageなど)を使用ギャップを識別するのにはいいですが、意味のあるテストの代価でカバレッジ数値を追うのはしない。エッジケース、典型的な使用、修正バグの回帰テストに焦点を当ててください。
セキュリティと安全考慮事項
-
可能な限りアンセーフを回避: Rust のセキュリティ保証は、セーフコードに留まるとき最も強力です。絶対に必要な場合(例えば、C とのやり取り、ボローチェッカーが理解できないデータ構造の実装、またはパフォーマンスクリティカルな低レベルの命令呼び出し)にのみ、コードのセクションをアンセーフとしてマークします。すべてのアンセーフブロックまたは関数は、Rust のセキュリティルール(データレース、無効なポインタ逆参照、不正なアライメント、ユースアフターフリーなし)をアップホールドしているというプロミスです。アンセーフを使用する場合は、カプセル化します。セーフ抽象化の背後に隠す、アンセーフがユーザーに漏らさないように。例えば、メモリプールを実装する場合、すべてのアンセーフをモジュール内に保ち、セーフ関数を内部的に安全インバリアントを確保する公開します。
-
アンセーフコードガイドラインに従う: アンセーフコードを必ず書く場合、新興 Rust Unsafe Code Guidelines や Rustonomicon(The Dark Arts of Unsafe Rust)を参照して、ベストプラクティスを習う。例えば、一つのガイドラインはアンセーフ関数のすべてのセキュリティ要件をドキュメント化(誰が呼び出すかはそれらをアップホールドする必要)を明確にコメントとドックで。別のガイドラインは、アンセーフブロックのサイズを最小化することです。アンセーフ操作をだけ内の実行してセーフコードで多くを行う。Miri(未定義の動作を検出できるインタープリター)を使用してアンセーフコードをテストして問題をつかむ。サニタイザーサポート(例えば、ASan または ThreadSanitizer、Rust フラグ経由)でメモリ問題をデバッグするときに実行します。
-
Clippy Lints: 一般的な間違いをキャッチしてコード品質を改善するため
cargo clippyを定期的に使用します。Clippy はコード正確性、パフォーマンス、スタイル、およびそれ以上のリンタのコレクションです。unwrap をコードで使用する場合(適切なエラー処理で扱うのに良い)、または効率的なメソッドが存在するとき、悪い方法を使用することについて警告できます。例えば、Clippy は手動ループ代わりに iter().any() を使用して要素を検索することを提案、またはあなたが Option または Result を不要にクローンするときキャッチするかもしれません。少なくともclippy::pedanticまたはclippy::nurseryリントを有効にしたい場合、より厳格なチェック、ただしいくつかのリントはきわめて主観的に気をつけてください。特定 Clippy リント があなたのプロジェクトに適用されないか、ノイズが多すぎる場合、それらを明示的に許可できます(属性またはクリッピー.toml config)。重要なことは Clippy を使用して慣用的なパターンを学び、潜在的なエラーをキャッチすることです(うっかり std::fmt::Debug の実装を忘れるか、浮動点数で直接 == を使用する)。 -
アンセーフを制限して監査: 可能な場合、アンセーフなしですませるクレート についてクレートレベルリント
#![forbid(unsafe_code)]を保つ。これはアンセーフを不意に導入しないことを確保します。プロジェクトがアンセーフを使用する必要がある場合(例えば、低レベルモジュール)、コードベースの小さい部分に分離することを検討します。コードレビューは、アンセーフブロックに特に注意を払ってください。外部監査ツールを使用するか、それらセクションについて徹底的に査読を持つ同僚。コミュニティの理念は、アンセーフがハイパフォーマンスまたは FFI のため必要な時がある認めることが、それは極端な注意と精査で処理されるべきことです。 -
メモリセーフティと所有権: 所有権モデルを抱きかかえることで、自然にバグの全体的なクラスを避けます(バッファオーバーフロー、二重フリー等)。C コードとやり取り或いは手動メモリ管理をしている場合、常に警惕してなるべく早くセーフ Rust タイプに生ポインタを変換します。例えば、C から生ポインタを得た場合、
slice::from_raw_partsなどのアンセーフブロックでそれをスライスにラップし、それからセーフに動作します。可変グローバル状態を避けます。グローバル状態が必要な場合、lazy_staticまたはonce_cellを使用してセーフに初期化、またはより良く、状態を関数を通して渡すようにリファクタリングします。スレッド・ゼロセーフを高レベルプリミティブを優先してデッドロックと他のスレッドセーフ問題を回避。例えば、どこにでもArc<Mutex<T>>を散らすのではなく、各スレッドまたはタスクが必要とするデータの排他的所有を持つように設計するのでスレッドタスクしてください。 -
セキュリティ監査とツール: 既知のセキュリティ脆弱性の依存関係をスキャンするために
cargo auditを使用します。RustSec advisory データベースは、使用しているクレートバージョンがセキュリティ欠陥を持つ場合、警告します。CI に cargo audit を統合(実行はクイック)して新しい脆弱性が開示されたときに通知されます。依存関係を最新に保つ、特にセキュリティフィックスについて。暗号化またはセキュリティセンシティブコードについて、よく査読されたクレート(crypto のための ring、または sodiumoxide)を使用することを優先して、あなたが専門知識を持つ限り独自に書かない。機密情報を処理する場合、サイドチャネル考慮事項に気をつけます(例えば、シークレットについて定時比較を使う)及び標準的なメモリをドロップで0にされていない事実(シークレット用 zeroize cレート使用)。 -
疑わしい構造のリント: Rust のコンパイラと Clippy は、セキュリティに役立つリントを提供します。例えば、コンパイラは未処理 must_use 結果について警告(多くの場合、処理されなかったエラー)、ロジック間違いをキャッチできます。Clippy は println! をライブラリで使用(デバッグレフトオーバーかもしれない)や dbg! マクロなどについて警告できます。リリースビルドについて
#![deny(warnings)]を有効化することを検討(ただし CI については、上記のように Clippy を使用するのがしばしば良い)。cargo-denyもあり、依存関係での license compliance や他のセキュリティ関連のポリシーをチェックできます。
コードレビューと CI 実践
-
コードレビュー焦点: Rust コードレビューで、正確さ、明確さ、慣用的な使用に焦点を当てる。すべての可能な失敗ポイント(ライブラリコードで .unwrap() または .expect() なし、アプリケーションコードでの利用は正当化される)にエラーハンドリングが済んでいることを確保します。関数がドキュメント、特に公開を持つことをチェック、及び命名は明確です。各アンセーフ利用がサウンドであることを検証(レビュアーはそれぞれ推論または説明を要求することを試みるべき見えないなら)。過度に複雑なコード;多くありますより簡単な慣用的なアプローチ(Clippy ヒントがここをガイド)。関数がコードカバレッジをテストで、テストと新しいコードをすべてパスを確保します。パフォーマンスが変更されたコードについて懸念事項であれば、ベンチマークが存在するか追加すべきか、変更がプロファイルされているか、議論してください。
-
CI での自動チェック: 各プルリクエストとメインへのプッシュで実行される継続統合パイプラインをセットアップします。CI は最低限実行すべき
cargo build(すべてのターゲットフィーチャーのクリーン環境でコンパイルするか確認)、cargo test(ドック・統合テスト含まれて)、cargo fmt -- --check(フォーマット実施するため)、及びcargo clippy(リント清潔さを実施)。clippy については、-D warnings を使用するかもしれない CI ビルドを失敗させるどんなリント警告も作ります。これは、マージされたすべてのコードが標準に自動的に従うことを保証します。さらに、CI でのcargo auditを実行して、依存関係脆弱性を早期にキャッチすることを検討します。プロジェクトがライブラリの場合、複数の Rust バージョン(特に MSRV を約束する場合、及び最新の安定版)で、可能ならば異なるプラットフォーム(Linux、Windows、Mac、またはこれらをサポートするなら WASM ターゲット)でテストすることができます。 -
継続的デプロイメント / 統合: 可能な場合、CI を実行して、追加の品質チェック:例えば、
cargo fuzzを使用してファジテスト(クラッシュまたはパニックケースをキャッチ)、proptest クレートでプロパティベースのテスト、またはステージング環境での統合テスト。これらはすべてのプルリクエストで実行されないかもしれませんが、毎晩や要望で実行されるかもしれません。ライブラリについて、例がコンパイルすることを確保(cargo test --examples)し、おそらくcargo docが警告なしで成功していることをます。 -
プルリクエスト チェックリスト: 貢献者に一般的なことを思い出させるプルリクエストテンプレートまたはチェックリストが有用です。ユーザーが直面する変更のため更新されたドキュメント、新フィーチャーまたはバグフィックスのためのテスト、スタイル ガイドラインの接着(ただし CI は整形/Clippy 問題をキャッチ)。これはレビュープロセスをスムーズに保つ。可能な場合、小さく焦点を当てたプルリクエストを奨励、これは徹底的にレビューするのにより簡単です。
-
CI アーティファクトとカバレッジ: オプションで、ドキュメント生成およびアップロードのために CI を使用します(docs.rs はすでにライブラリのリリースに対してドックを構築)。アプリケーション、CI はクロスコンパイルまたはマトリックスビルド))を使用して様々なターゲットについてバイナリを構築し、セキュリティ監査または静的解析を実行することもできます。いくつかのプロジェクトは新しいコードが最小のテストカバレッジを持つか、少なくともカバレッジを減らさないことを強制。Codecov のようなツールを統合するためにプルリクエスト上で報告することができます。コードカバレッジは完璧なメトリックスではありませんが、テストされていないコードパスを特定するのに役立つことができます。
-
リリース慣行: リリースする時期になった(ライブラリについて crates.io に発行;バイナリについて、新しいバージョン/タグをカット)、Cargo.toml のバージョンを semantic バージョニング(ライブラリの場合)またはリリース準備に従ってアップデートします。CI(またはローカルテスト)はコンパイルしたいコマンドについてフルテストスイートを実行して、ほぼリント実行してリリースモードを確保します。いくつかのパフォーマンスセンシティブなテストは
cargo test --releaseを使用することを検討(ただし、ほとんどのロジックテストはデバッグで良くします)。デプロイメント及び CI が必要に応じて発行またはアーティファクトをビルドして、自動化により(例えば、CI でのcargo publish正しい認証情報を使用しライブラリ)、またはバイナリを構築や GitHub リリースに付加します。
これらのガイドラインに従うことで - 明確で慣用的なコード記述、堅牢なエラーハンドリング、賢い最適化、Rust の並行処理を安全に使用、プロジェクトをきれいに構造化、思慮深い依存関係管理、徹底的なドキュメント、CI を活用 - あなたは信頼性があって、保守しやすい、効率的で、での仕事を楽しめる Rust コードを作成できます。幸いな Rust!
ワークフロー
これは、Rust プロジェクトでこれらのベストプラクティスを適用する段階的なワークフローです:
-
初期セットアップ:
cargo newで新しいプロジェクトを開始。すぐにバージョン管理(例えば git)をセットアップし、プッシュの各テスト、整形、及び Clippy を実行する CI コンフィギュレーション(GitHub Actions、GitLab CI等)を追加。開発環境内で rustfmt と Clippy を有効化(rustup component add rustfmt clippy)。 -
**規約を確立:
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- sheinsight
- リポジトリ
- sheinsight/spack
- ライセンス
- MIT
- 最終更新
- 2026/5/7
Source: https://github.com/sheinsight/spack / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。