# Rust Modeling

> Rust type and error modeling process. Designs domain enums, error hierarchies, and ownership patterns before writing logic. Use when this capability is needed.

- Skill: `tomevault-io/rust-modeling` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/rust-modeling`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/rust-modeling/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/rust-modeling

---


When this skill is active, follow this 6-step discipline for designing Rust types and error handling:

## 1. Map the Domain to Types

Before writing any logic, sketch the type graph:
- **List every entity and relationship** — draw a dependency diagram (even in comments)
- **One struct per entity, one enum per variant set** — if two concepts have different lifecycles, they're separate types
- **Decide visibility upfront**: which types are `pub`? Which fields? Start private, widen only when needed
- **Write type signatures first** — `fn process(order: &Order) -> Result<Receipt, OrderError>` before any body

## 2. Design the Error Hierarchy

Build the error tree before writing fallible code:
- **Enumerate failure modes**: list every way each operation can fail — network, parse, validation, not-found
- **Group by recovery strategy**: errors the caller can retry vs errors that are fatal determine your variant structure
- **Map external errors at boundaries** — don't leak third-party types through public APIs; wrap them in domain errors
- **Write the `Display` messages for humans**: each `#[error("...")]` string should be actionable, not just descriptive

## 3. Encode Invariants in the Type System

Make illegal states unrepresentable — push validation into types:
- **Typestate pattern**: `struct Order<S: State>` with `impl Order<Draft>` and `impl Order<Confirmed>` — impossible to ship an unconfirmed order
- **Ask "can this field be invalid?"** — if yes, use a newtype that validates on construction
- **NonZero types** for values that must not be zero: `NonZeroU32` for counts, ports, IDs
- **Builder pattern** for types with many optional fields — `Default` + method chaining

## 4. Model Ownership and Borrowing

Decide ownership boundaries before writing implementations:
- **Draw the ownership tree**: which struct owns which data? Where are the borrows?
- **Annotate each function signature** with `&self`, `&mut self`, or `self` — this determines your API's flexibility
- **Identify shared-state points**: if two subsystems need the same data, decide channels vs `Arc` before coding
- **Benchmark before cloning**: if you reach for `.clone()`, measure whether borrowing is feasible first

## 5. Write Conversion Traits

Define how types transform between layers:
- `From<X>` for infallible conversions (e.g., domain type → API response)
- `TryFrom<X>` for fallible conversions (e.g., raw input → validated domain type)
- Keep conversions in a `convert.rs` or `impl` block near the target type
- Test every `TryFrom` with both valid and invalid inputs

## 6. Verify with the Compiler

Let `cargo` prove the design is sound:
- `cargo check` — zero warnings (`#![warn(clippy::all, clippy::pedantic)]` in `lib.rs`)
- `cargo clippy -- -D warnings` — treat all clippy lints as errors
- `cargo test` — all type-level assertions and conversion tests pass
- If the compiler fights you, the model is wrong — redesign the types, don't add `unwrap()` or `clone()`
- If any step fails, fix the design and re-run the entire chain

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/fricklers) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-15 -->

