Library Development Standards
高品質で汎用性が高く、メンテナンスしやすい Rust ライブラリを構築するための設計思想と指針。この文書は、新たな知見が得られるたびに更新・進化し続けるものである。
1. 抽象代数的設計 (Abstract Algebraic Design)
特定の型(i64 等)やそのドメイン(定義域)への依存を排除し、計算に本質的に必要な「代数的性質」のみを要求する。
- ドメイン依存の排除:
From<i64>を避ける本質的な理由は、型Tが「整数そのもの」であるという強い仮定を排除することにある。 - 「変換」から「作用・系内部の演算」への転換:
- スカラー倍の作用 (
Mul<i64>): 整数 $n$ をTに「変換」して掛けるのではなく、Tに対して整数 $n$ が「作用(スカラー倍)」できることを要求する。 - 系内部での単位元生成 (
Sum,Product):0.into()や1.into()の代わりに、代数系が持つ演算ルールから単位元を導き出す。具体的には、モジュール内に定義したfn zero<T: Sum>() -> T { std::iter::empty::<T>().sum() }やfn one<T: Product>() -> T { std::iter::empty::<T>().product() }を使用する。num-traitsのZero/Oneを避ける理由は、ac-library-rsのModInt等がこれらを実装していない場合があるためである。
- スカラー倍の作用 (
- インターフェースの純粋化: 型の「ドメイン」ではなく「能力」に基づいてトレイト境界を定義する。
2. 構造的対称性の維持 (Structural Symmetry)
対になる概念(最小/最大など)を扱うファイル群は、ミラー構造を維持する。
- クローン構造の追求: 行数、空行の位置、インポートの順序、メソッドの定義順を完全に一致させる。
- メンテナンス性の同期:
symmetry-syncerスキルを用い、スタイル上のノイズを徹底的に排除する。
3. スニペット設計・実装指針 (Snippet Design and Implementation)
cargo-snippet で展開されるコードの品質と、展開後のコンパイル成功を保証するための基準。
3.1 設計指針 (Architectural Design)
- 依存グラフの最小化:
cargo-snippetで展開されるコードの依存グラフを最小限に保つ。 - 依存方向の管理: 具体的な機能を提供するスニペット(例:
RangeAffineDualSegtree)側でinclude属性を使い、汎用的なコアスニペットを呼び出す形にする。これにより、コア実装が特定の具体例に汚染されるのを防ぐ。 - 外部トレイト実装の分離: 自作の型に対して、別のスニペットで定義されたトレイトを実装する場合(例:
ExtIntへのBoundedAbove実装など)、その実装自体を独立したスニペット(例:mod_ext_int_bounded)として定義する。これにより、そのトレイトを必要としない利用シーンでコアスニペットの依存関係が増えるのを防ぐ。 - 統合テストの配置: 原則として「依存する側」のファイルにテストを記述し、コア側に不要な依存が混入するのを防ぐ。
- 可視性 (Visibility): ライブラリ内部でのみ使用される補助構造体は、外部に露出させないよう**非公開(可視性修飾子なし)**として定義する。スニペットとして
main.rs等の単一クレート内に展開された際、他所から意図せずアクセス可能になるのを防ぐため、pub(crate)も使用しないこと。
3.2 実装規約 (Implementation Conventions)
スニペットは提出時にフラットなコンテキスト(main.rs 等)に展開される。この展開プロセスを考慮した実装を行う。
- 属性の記述形式: 複数の属性引数(
prefix,include等)がある場合、#[snippet(prefix = "...", include = "...")]のように1行にまとめて記述する。 - インポートの配置:
std/ac_library等の外部依存: スニペット貼り付け先でも常に利用可能であるため、#[snippet]が付与されたmodブロックの内部で直接use宣言を行う。- ローカルスニペットへの依存:
- ファイルの先頭(
pub modの外)でcrate::...を用いて、依存する型やトレイトをインポートする(IDE/開発環境用)。 modブロックの内部でuse super::{SymbolA, SymbolB};のように、必要なシンボルを明示的にインポートする。include属性で依存関係を紐付ける。
- ファイルの先頭(
- 禁止事項とその理由:
use crate::...の禁止:snippet_linterにより厳禁。展開後はcrateが存在しないため、必ず相対パス(super)を使用する。use super::*の禁止:cargo-snippetはmodブロックの外側を切り捨てるため、ブロック外の依存関係はすべて失われる。*を使うと「何に依存しているか」がスニペット単体で不明確になり、かつ展開先のルートにある無関係なシンボルをすべて引き込んでしまうため、必ずシンボルを個別に指定する。
4. コーディング規約 (Coding Conventions)
4.1. rand クレートの利用
- バージョン:
0.9.2を使用。 - メソッド:
rng.random_rangeを使用(gen_rangeは不可)。 - 初期化:
from_os_rngを使用(from_entropyは不可)。
4.2. ランダムテスト (Random Testing)
- 基本方針: 素朴な実装(Naive Implementation)を「正解(Oracle)」とし、ランダムな操作群に対して実行結果が一致することを検証する。
- 密度設計: 入力値の生成範囲をあえて狭く設定することで、衝突や境界条件の発生頻度を高める。
- 属性: ランダムテストを実装する際には必ず
#[ignore]属性を付与し、通常のビルド・テストサイクルを妨げないようにすること。 - 実装の詳細: random-testing.md を参照。
4.3. コメント
- 既存のコードを編集、または既存のコードから新しいコードを作るとき、既存のコードのコメントを消してはいけません。
- 日本語で記述する。
- 「何をしているか (What)」ではなく、コードが存在する「なぜ (Why)」やロジックの「要点」を説明する。
- コードから直接読み取れる自明なコメント(ノイズ)は書かない。
- 「ここを変更」のような将来的に不要になる一時的なコメントは避ける。
4.4. コーディングスタイル
- できるだけ手続き的ではなく宣言的なコードを書く。
mutableな変数よりもimmutableな変数を優先する。ただし、可読性や計算量の観点で手続き的・mutableな実装が勝る場合はその限りではない。
4.5. ドキュメントコメント (Doc comments)
- 形式:
///を使用し、Markdown 形式で記述する。 - 要約と詳細の分離:
- 1行目の要約: 最初の1行はアイテムの機能を簡潔に示す要約文とする。
- 空行の強制: 要約文と、その後に続く詳細な説明や例示の間には必ず空行を挿入する。空行がない場合、レンダリング時にこれらが1行に結合されてしまうため。
- 例外: 箇条書き(
-や*)などの Markdown のブロック要素が続く場合は、空行がなくても結合されないため、必ずしも空行は必要ない。
- LaTeX 形式: ドキュメントコメント内では
$O(N \log N)$のように LaTeX 形式で記述する。 - 変数の統一: 実装上の変数名(
size,len,entries.len()等)にかかわらず、データサイズを表す記号には原則として $N$ を使用する。ただし、$N$ が何を表しているかを補足すること。
4.6. 標準トレイトの具備 (Standard Trait Implementation)
データ構造や値の型には、原則として以下の標準トレイトを実装する。
Clone: 原則として全ての型で実装する。Copy: 内部フィールドがすべてCopy可能である型において、原則として実装する。Debug: デバッグ効率のため必須とする。Default: 自然で、かつそのまま実用的な初期状態が定義できる場合にのみ実装する。- 引数なしで作成してすぐに利用可能であれば実装する(例:動的なコンテナ、零元・単位元を持つ値の型)。
- 初期化時にサイズや対象の指定が必須なもの(例:
SegmentTree,FenwickTree,Dsu等)は、デフォルト状態では機能せず実用性がないため、誤用を防ぐためにも実装してはならない。
PartialEq,Eq,Hash: 状態をキーとしてHashMapやHashSetで管理(メモ化等)できるように、可能な限り実装する。ただし、以下の点に注意する。- 決定論性の確保: 内部状態の走査順序が非決定的な型(例:内部に
HashMapを持つ型)は、ハッシュ値の整合性を保てないため、原則としてHashを実装しない。 - 等価性の定義が自明でない場合: 内部状態(評価待ちの遅延更新値など)に依存して、「論理的には等価だが構造的には異なる状態」が容易に生じ得る型は、利用者の混乱を招くため
PartialEqやEqを実装しない。
- 決定論性の確保: 内部状態の走査順序が非決定的な型(例:内部に
4.7. ユニットテスト (Unit Testing)
- 正常系: 基本的な追加、削除、検索を網羅する。
- エッジケース: 空の状態、要素数 1、サイズ 0 の初期化、重複要素、境界値を網羅する。
- パニックテスト:
#[should_panic]を用い、不正な入力(範囲外アクセス等)で正しく落ちることを確認する。
5. シンボル名変更時の整合性維持 (Naming Consistency)
リファクタリングにより型、関数、定数などのシンボル名を変更した際は、ドキュメントコメントやテスト関数名も一貫性を持って同期させる。
6. 代数演算子の設計指針・エラーハンドリング (Algebraic Operator Design & Error Handling)
- 無限大と 0 の乗算: Z-加群としての作用を模す場合、原則として $ \infty \times 0 = 0 $ と定義する(「無限大を 0 回足した結果は単位元である」という解釈)。
- 異常系の即時パニック: メソッドの前提条件(インデックス範囲、引数のドメイン等)を外れる入力に対しては、妥協してデフォルト値を返すのではなく、速やかに
panic!させる。
7. 既存の記載の尊重と意図しない削除の防止 (Respect for Existing Content and Prevention of Unintentional Deletion)
元の実装にある記載を尊重し、意図しない削除を徹底的に防ぐ。
- 既存資産の維持:
- 元の実装に含まれているテスト、コメント、関数などを、明確な理由なく削除してはならない。
- 削除時の提案プロセス:
- コードの整理に伴い既存の要素を削除すべきと判断した場合は、独断で削除せず、必ず理由を添えてユーザーに提案し、承諾を得ること。
- git diff による検証:
- 実装前後の
git diffを入念に確認し、元の実装にあった記載(テストケースやコメントなど)が意図せず失われていないかを必ず検証する。
- 実装前後の
8. 開発安全原則 (Development Safety Principles)
作業中の手戻りを最小限に抑え、確実に進捗を得るために以下の原則を遵守してください。
8.1. 段階的な修正 (Gradual Modification)
同じ内容の修正を複数箇所で行う場合は、一気にすべてを直す前に 1〜2 箇所で検証を行い、問題がないことを確認してから全体に適用すること。
- 予期せぬ副作用やロジックの不備があった際の手戻りを最小限に抑えるため。
8.2. インクリメンタルな機能実装 (Incremental Implementation)
大きめの機能を実装する場合は、最初からすべてを実装せず、最低限のコア機能から実装して段階的に機能を追加していくこと。
- 手戻りが発生した際のロスを小さくし、進捗を確実に検証可能な状態に保つため。
8.3. 検証プロトコル (Validation Protocol)
- 自動検証の必須化:
src/mylib配下のコードを編集した後は、必ずcode-verifierスキルを実行し、テスト・カバレッジ・静的解析・フォーマットを一貫して検証すること。
9. 指針の継続的改善 (Continuous Improvement)
- 知見が得られるたびに本スキルを更新し、設計品質を底上げする。
Converted and distributed by TomeVault — claim your Tome and manage your conversions.