m05-type-driven について
型で不変条件を表し、無効な状態を防ぐための Rust 設計スキルです。newtype から Builder、型状態パターンまで、よく使う型設計の選択指針を示します。
主な機能
- 01型で制約を表現
- 02一般的な型パターンの選択
- 03型状態設計の適用
- 04型設計のアンチパターンの特定
活用シーン
- 01newtype で意味のある値を表現
- 02コンパイル時の状態遷移をモデル化
- 03検証付き構築パターンの選択
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ actionbook/ rust-skills --skill m05-type-drivenインストール後、Claude Code で/m05-type-drivenを入力して読み込まれたか確認します。
詳細
型駆動設計
レイヤー 1:言語の仕組み
中心となる問い
型システムによって、無効な状態をどう防げるでしょうか?
実行時チェックに頼る前に、次の点を検討してください。
- コンパイラーでこのエラーを検出できますか?
- 無効な状態を表現できないようにできますか?
- 型で不変条件を表現できますか?
エラー → 設計上の問い
| パターン | ただこう言うのではなく | 代わりにこう問いかける |
|---|---|---|
| プリミティブ型への執着 | 「ただの文字列です」 | この値は何を表していますか? |
| ブール値フラグ | 「is_valid フラグを追加します」 | 状態を型で表せますか? |
| どこでも Optional | 「None かどうか確認します」 | 本当に値が存在しないことはあり得ますか? |
| 実行時の検証 | 「無効なら Err を返します」 | 生成時に検証できますか? |
思考のヒント
実行時検証を追加する前に、次の点を確認してください。
-
型で制約を表現できますか?
- 数値の範囲 → 範囲制限型または newtype
- 有効な状態 → 型状態パターン
- 意味上の区別 → newtype
-
検証はいつ可能ですか?
- 生成時 → 検証済み newtype
- 状態遷移時 → 型状態
- 実行時にのみ可能 → 明確なエラーを伴う Result
-
不変条件を知る必要があるのは誰ですか?
- コンパイラー → 型レベルで表現
- API の利用者 → 明確な型シグネチャ
- 実行時にのみ必要 → ドキュメント
上へたどる ↑
型設計が不明確な場合:
"Need to validate email format"
↑ Ask: Is this a domain value object?
↑ Check: m09-domain (Email as Value Object)
↑ Check: domain-* (validation requirements)
| 状況 | たどる先 | 問い |
|---|---|---|
| 作成する型 | m09-domain | ドメインモデルは何ですか? |
| 状態マシンの設計 | m09-domain | 有効な遷移は何ですか? |
| マーカートレイトの使用 | m04-zero-cost | 静的ディスパッチですか、動的ディスパッチですか? |
下へたどる ↓
設計から実装へ:
"Need type-safe wrapper for primitives"
↓ Newtype: struct UserId(u64);
"Need compile-time state validation"
↓ Type State: Connection<Connected>
"Need to track phantom type parameters"
↓ PhantomData: PhantomData<T>
"Need capability markers"
↓ Marker Trait: trait Validated {}
"Need gradual construction"
↓ Builder: Builder::new().field(x).build()
クイックリファレンス
| パターン | 目的 | 例 |
|---|---|---|
| Newtype | 型安全性 | struct UserId(u64); |
| 型状態 | 状態マシン | Connection<Connected> |
| PhantomData | 分散/ライフタイム | PhantomData<&'a T> |
| マーカートレイト | 能力フラグ | trait Validated {} |
| ビルダー | 段階的な構築 | Builder::new().name("x").build() |
| シールドトレイト | 外部からの実装を防止 | mod private { pub trait Sealed {} } |
パターンの例
Newtype
struct Email(String); // Not just any string
impl Email {
pub fn new(s: &str) -> Result<Self, ValidationError> {
// Validate once, trust forever
validate_email(s)?;
Ok(Self(s.to_string()))
}
}
型状態
struct Connection<State>(TcpStream, PhantomData<State>);
struct Disconnected;
struct Connected;
struct Authenticated;
impl Connection<Disconnected> {
fn connect(self) -> Connection<Connected> { ... }
}
impl Connection<Connected> {
fn authenticate(self) -> Connection<Authenticated> { ... }
}
選択ガイド
| ニーズ | パターン |
|---|---|
| プリミティブ型の型安全性 | Newtype |
| コンパイル時の状態検証 | 型状態 |
| ライフタイム/分散のマーカー | PhantomData |
| 能力フラグ | マーカートレイト |
| 段階的な構築 | ビルダー |
| 実装の集合を限定 | シールドトレイト |
| ゼロサイズ型のマーカー | ZST 構造体 |
アンチパターン
| アンチパターン | 問題点 | よりよい方法 |
|---|---|---|
| 状態をブール値フラグで表す | 実行時エラー | 型状態 |
| 意味上の型に String を使う | 型安全性がない | Newtype |
| 未初期化を Option で表す | 不変条件が不明確 | ビルダー |
| 不変条件があるのにフィールドを公開する | 不変条件違反 | 非公開フィールド+検証済みの new() |
関連スキル
| 場面 | 参照先 |
|---|---|
| ドメインモデリング | m09-domain |
| トレイト設計 | m04-zero-cost |
| コンストラクターでのエラー処理 | m06-error-handling |
| アンチパターン | m15-anti-pattern |
コメント
Markdown 対応