Przejdź do treści
Biblioteka skilli
Programowanie · Nauka i wyjaśnianie

type-driven-design

/type-driven-design

Modeluje typy w Rust tak, by niepoprawne stany były nie do wyrażenia: enumy zamiast flag, newtypy i typestate dla kolejności protokołu.

Repozytorium
skills ↗
Licencja
BSD-3-Clause
Ostatnia zmiana repozytorium
16.08.2026

Ocena i bezpieczeństwo

95/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść30 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
4
Skrypty
0
Komentarze HTML
0
Adresy URL w SKILL.md
0
Przestarzałość

Brak sygnałów. Repozytorium zmienione 16.08.2026, bez odwołań do wycofanych modeli i API.

Licencja

BSD-3-Clause, otwarta.

Co robi i kiedy

Modeluje typy w Rust tak, by niepoprawne stany były nie do wyrażenia: enumy zamiast flag, newtypy i typestate dla kolejności protokołu.

Oryginalny opis (EN)

Make illegal states unrepresentable — enums instead of boolean and string flags, newtypes instead of bare primitives, parsed types instead of validated ones, typestate for protocol order. Use when a struct has fields that are only valid in some combinations, when validation is re-checked at many call sites, when boolean flags multiply, or when the user asks how to model a domain in Rust.

Jak działa

  1. The principle
  2. Enums over flag soup
  3. Parse, do not validate
  4. Newtypes as invariant carriers
  5. Typestate for protocol order
  6. When to stop
  7. Deferrals
  8. Verification
Sekcje z nagłówków SKILL.md, w oryginale · 120 linii, 796 słów
Podgląd SKILL.md
---
name: type-driven-design
description: Make illegal states unrepresentable — enums instead of boolean and string flags, newtypes instead of bare primitives, parsed types instead of validated ones, typestate for protocol order. Use when a struct has fields that are only valid in some combinations, when validation is re-checked at many call sites, when boolean flags multiply, or when the user asks how to model a domain in Rust.
---

# Type-Driven Design

A type is a promise about which states exist. This skill changes *what the types
allow* — it is the only skill in the Rust bucket that restructures a domain
model, and it does so with a stopping rule: encode an invariant in a type when
violating it is a real bug class in this codebase, not when it is merely
expressible. How a rejection is reported is `/rust-errors`; what a change to a
published type costs is `/rust-api-design`.

## The principle

A type that cannot represent the bad state removes the runtime check, the test
for it, and the bug report about it. Ask of every struct: how many field
combinations are constructible, and how many are valid? The gap between those two
numbers is the surface where bugs live.

## Enums over flag soup

Two booleans make four states; if only three are valid, the fourth is a latent
bug:

```rust,ignore
// Four states, three valid: is_draft && is_published is nonsense.
struct Post {
    is_draft: bool,
    is_published: bool,
    published_at: Option<DateTime<Utc>>,
}

// Three states, three valid, and published_at cannot go missing.
enum Post {
    Draft { body: String },
    Scheduled { body: String, at: DateTime<Utc> },
    Published { body: String, at: DateTime<Utc> },
}
```

The same move covers the `Option<T>` pair smell: two `Option` fields where
exactly one is always `Some` is an enum with two variants, and the impossible
combination — both `None` — stops being constructible.

## Parse, do not validate

A function that takes `&str` and checks it is a valid email address checks again
at the next call site, and the next, because the type carries no proof. A
function that takes `Email` — constructible only through
`Email::parse(&str) -> Result<Email, EmailError>` — checks once at the boundary
and never again. The type is the proof the check ran.

```rust,ignore
struct Email(String); // private field: the constructor is the only way in

impl Email {
    fn parse(input: &str) -> Result<Self, EmailError> {
        // the check, exactly once
    }
}

fn deliver(to: &Email) { /* no validation possible here — none needed */ }
```

The private field is the load-bearing part. A public field makes the
constructor a suggestion, and the invariant dies the day someone builds the
struct by literal.

## Newtypes as invariant carriers

`/idiomatic-rust` reaches for a newtype to stop two arguments of the same
primitive being swapped at a call site. Here the newtype *carries an invariant*:
`NonEmptyVec<T>`, `Percentage`, `Sanitized<String>` — types whose constructor is
the only entrance and whose fields are private. The standard library already
runs this pattern: `NonZeroU32` and friends, with the niche-optimization payoff
that `Option<NonZeroU32>` is the same size as `u32`.
The same argument applies to numbers, where the type system will otherwise let
a byte count be added to a millisecond count — `NonZero` for what cannot be
zero, `TryFrom` rather than `as` at every boundary, and `NUMERICS.md` for the
rest.

## Typestate for protocol order

When operations must happen in sequence — connect before send, build before
finalize — the stage can be encoded in a type parameter, so calling out of order
is a compile error rather than a runtime `Err`. The full worked example, the
builder variant, and the costs live in `TYPESTATE.md`; the one-line version is
that typestate is for small, fixed state machines where out-of-order use is a
real bug class.

## When to stop

Type-level modelling has a cost: worse error messages, heavier generics, and a
refactor tax when the domain changes. A five-parameter typestate builder for a
struct built once in `main` is over-engineering, and this skill says so rather
than leaving the call to a reviewer.

## Deferrals

How the constructor reports rejection is `/rust-errors`. Whether the resulting
type is ergonomic for callers, and whether tightening a type is a semver break,
is `/rust-api-design`. If the model change is being forced by borrow-checker
pain, run `/ownership-not-clone` first — the answer may be ownership, not
modelling.

## Verification

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

Run clippy at the lint level configured in the repo — never on top of one the
repo already set. Then the specific evidence this skill claims: after the
change, the runtime checks the type made impossible should be *deleted*, not
left in place. Grep for the now-impossible branch and confirm it is gone, and
confirm the tests that asserted the old runtime rejection were removed or
rewritten to a compile-fail expectation. If the repo already uses `trybuild`,
add a compile-fail case proving the illegal construction no longer compiles; if
it does not, do not introduce the dependency as part of this change — state the
compile-fail expectation in a doc comment instead.
Pliki w paczce (4)
  • NUMERICS.md 4 KB
  • SKILL.md 5 KB
  • TYPESTATE.md 4 KB
  • agents/openai.yaml 1 KB

Wersje

W bazie nie ma innych skilli o nazwie type-driven-design.

Migawka bazy z 28.10.2026 · wersja paczki 73bfcac43500 (skrót SHA-256 całej paczki)

Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.

Podobne skille

  • 90type-driven
    Pomaga modelować domenę, maszyny stanów i API tak, aby niepoprawne stany były niemożliwe do wyrażenia, oraz parsować dane zamiast je tylko walidować.
  • 100parse-dont-validate
    Stosuje zasadę projektowania opartą na typach: zamienia dane niestrukturalne w typy strukturalne na granicach systemu, tak by stany niedozwolone były niereprezentowalne, przy walidacji, typach danych i obsłudze błędów.
  • 90design
    Projektuje API, schematy, typy i automaty stanów tak, by błędne użycie nie dawało się wyrazić: typy brandowane, unie dyskryminowane, typestate i parse-don't-validate.
  • 100rust-best-practices
    Sprawdza architekturę Rust i granice crate'ów pod kątem wzorców projektowych: typestate, sealed traits, kontrakty błędów i poprawność typów.
  • 90type-design-review
    Przegląda modele danych, sygnatury, typy domenowe, parsowanie, rzutowania i maszyny stanów, by niepoprawne stany były trudne do wyrażenia.
  • 95rc-typescript
    Projektuje typy TypeScript tak, by nielegalne stany były niereprezentowalne, bez furtek typowania.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.