rust-type-system
Skill1 starsChanged 6 months agoMITView on GitHub
Rust type-driven design — make invalid states unrepresentable. Use this skill when: - User asks about newtype pattern, wrapper types, or branded types - User implements state machines, typestate pattern, or compile-time validation - User uses PhantomData, marker types, ZST, or phantom types - User asks "how do I prevent misuse of my API at compile time?" - User works with sealed traits, non-exhaustive enums, or repr(transparent) - User wants to encode constraints (validated email, positive integers, non-empty Vec) - User asks about generic type parameters, associated types, or GATs - User implements the builder pattern with mandatory fields Trigger when the user wants the compiler to enforce invariants.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- botirkhaltaev/botir-skillstheir own version, first seen Jul 24, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- botirkhaltaev/botir-skills★ 4same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cliSkill
- rust-cloud-nativeSkill
- rust-concurrencySkill
- rust-databaseSkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-fintechSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-design-patternsSkill
- rust-performanceSkill
- rust-securitySkill
- rust-testingSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 6 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 29
Before reaching for if checks or runtime panics:
- Very specific: line 337
Use the never type ! to indicate computations that diverge (i.e.
- Very specific: line 347
Use std::marker::PhantomData to tell the compiler that a struct behaves as if it owns a value of type T even if it only uses it at compile …
- Distinctive: line 403
Use newtypes for domain IDs, units, validated strings, and external identifiers.
- Distinctive: line 404
Prefer enums over stringly-typed or boolean state.
- Distinctive: line 405
Use typestate only when it prevents real misuse at compile time.
- Distinctive: line 406
Keep marker types zero-sized and private unless downstream users need them.
- Distinctive: line 407
Document invariants that unsafe code or FFI depends on.
- Distinctive: line 408
Add compile-fail examples or tests for APIs whose safety comes from type restrictions.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Quick Navigation
- references/typestate.md
- references/phantom_markers.md
Rust Type-Driven Design
Make invalid states unrepresentable. If the compiler can catch it, don't leave it to runtime.
Core Question
Before reaching for if checks or runtime panics:
- Can the compiler reject invalid inputs?
- Can invalid states be unrepresentable in the type system?
- Can I validate at construction and trust invariants everywhere else?
Newtype Pattern
Wrap primitives to create distinct, incompatible types:
// Without newtypes: easy to swap arguments
fn transfer(from: u64, to: u64, amount: u64) { ... }
transfer(order_id, user_id, cents); // Compiles! Bug at runtime.
// With newtypes: compiler catches swaps
struct UserId(u64);
struct OrderId(u64);
struct Cents(u64);
fn transfer(from: UserId, to: UserId, amount: Cents) { ... }
transfer(order_id, user_id, amount); // E0308: mismatched types ✓Newtype with Validation (Parse, Don't Validate)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Email(String);
impl Email {
pub fn new(s: &str) -> Result<Self, EmailError> {
// Validate ONCE at the boundary
if s.contains('@') && s.split('@').nth(1).map_or(false, |d| d.contains('.')) {
Ok(Email(s.to_lowercase()))
} else {
Err(EmailError::Invalid(s.to_string()))
}
}
pub fn as_str(&self) -> &str { &self.0 }
}
// Deref for ergonomic access to String methods
impl std::ops::Deref for Email {
type Target = str;
fn deref(&self) -> &str { &self.0 }
}
// Now: every Email is KNOWN to be valid. No re-checking needed.
fn send_email(to: &Email, body: &str) { /* trust it's valid */ }Non-Empty Collections
pub struct NonEmpty<T> {
head: T,
tail: Vec<T>,
}
impl<T> NonEmpty<T> {
pub fn new(head: T) -> Self { Self { head, tail: vec![] } }
pub fn push(&mut self, item: T) { self.tail.push(item); }
// Infallible — always returns something (unlike Vec::first())
pub fn first(&self) -> &T { &self.head }
pub fn len(&self) -> usize { 1 + self.tail.len() }
}Typestate Pattern
Encode state transitions in the type system. Invalid transitions don't compile.
// State marker types (zero-sized, no runtime cost)
struct Draft;
struct PendingReview;
struct Published;
struct Post<State> {
content: String,
_state: std::marker::PhantomData<State>,
}
// Only Draft posts can be edited
impl Post<Draft> {
pub fn new(content: String) -> Self {
Self { content, _state: std::marker::PhantomData }
}
pub fn edit(&mut self, content: String) {
self.content = content;
}
pub fn submit(self) -> Post<PendingReview> {
Post { content: self.content, _state: std::marker::PhantomData }
}
}
// Only PendingReview posts can be published or rejected
impl Post<PendingReview> {
pub fn approve(self) -> Post<Published> {
Post { content: self.content, _state: std::marker::PhantomData }
}
pub fn reject(self) -> Post<Draft> {
Post { content: self.content, _state: std::marker::PhantomData }
}
}
// Only Published posts can be read
impl Post<Published> {
pub fn content(&self) -> &str { &self.content }
}
// Compile-time enforcement:
let post = Post::new("Hello".into());
post.edit("Updated".into()); // OK
let post = post.submit();
// post.edit("No!"); // ERROR: Post<PendingReview> has no edit()
let post = post.approve();
println!("{}", post.content()); // OKTypestate Builder (Required Fields at Compile Time)
// Marker types for builder state
struct NoUrl;
struct HasUrl(String);
struct NoMethod;
struct HasMethod(Method);
struct RequestBuilder<U, M> {
url: U,
method: M,
headers: Vec<(String, String)>,
timeout_secs: u64,
}
// Start with no required fields set
impl RequestBuilder<NoUrl, NoMethod> {
pub fn new() -> Self {
Self { url: NoUrl, method: NoMethod, headers: vec![], timeout_secs: 30 }
}
}
// Set URL — transitions to HasUrl state
impl<M> RequestBuilder<NoUrl, M> {
pub fn url(self, url: impl Into<String>) -> RequestBuilder<HasUrl, M> {
RequestBuilder { url: HasUrl(url.into()), method: self.method,
headers: self.headers, timeout_secs: self.timeout_secs }
}
}
// Set method — transitions to HasMethod state
impl<U> RequestBuilder<U, NoMethod> {
pub fn method(self, method: Method) -> RequestBuilder<U, HasMethod> {
RequestBuilder { url: self.url, method: HasMethod(method),
headers: self.headers, timeout_secs: self.timeout_secs }
}
}
// Optional fields available in any state
impl<U, M> RequestBuilder<U, M> {
pub fn header(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
self.headers.push((key.into(), val.into()));
self
}
pub fn timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
}
// build() ONLY available when both URL and Method are set
impl RequestBuilder<HasUrl, HasMethod> {
pub fn build(self) -> Request {
Request { url: self.url.0, method: self.method.0,
headers: self.headers, timeout_secs: self.timeout_secs }
}
}
// Usage:
let req = RequestBuilder::new()
.url("https://api.example.com/data")
.method(Method::GET)
.header("Authorization", "Bearer token")
.build(); // Compiles ✓
// RequestBuilder::new().build(); // E0599: no method build() ✓State Machine with Enums
For runtime state (when typestate is too rigid):
#[derive(Debug)]
pub enum Connection {
Disconnected,
Connecting { attempt: u32, started_at: std::time::Instant },
Connected { session_id: String, established_at: std::time::Instant },
Reconnecting { attempts: u32, last_error: String },
}
impl Connection {
pub fn connect(&self) -> Result<Connection, ConnectError> {
match self {
Connection::Disconnected => Ok(Connection::Connecting {
attempt: 1,
started_at: std::time::Instant::now(),
}),
Connection::Connected { .. } => Err(ConnectError::AlreadyConnected),
other => Err(ConnectError::InvalidState(format!("{other:?}"))),
}
}
pub fn is_usable(&self) -> bool {
matches!(self, Connection::Connected { .. })
}
}
// Make illegal transitions explicit in code:
// You cannot call send() on a Disconnected connection
// because send() takes &Connection and does the state check.PhantomData
PhantomData<T> adds type information without runtime cost:
use std::marker::PhantomData;
// Token that is valid only for a specific user's session
struct Token<User> {
value: String,
_user: PhantomData<User>, // Zero bytes at runtime
}
struct AdminUser;
struct RegularUser;
fn admin_action(token: &Token<AdminUser>) { /* only admins */ }
// Token<RegularUser> cannot be passed to admin_action
// The types are distinct even though the struct is the same.Sealed Traits
Prevent external implementations — stable extension point without full openness:
mod private {
pub trait Sealed {} // Private, so external crates can't implement it
}
pub trait DatabaseDriver: private::Sealed {
fn execute(&self, query: &str) -> Result<Rows, DbError>;
}
// Only types in this crate implement Sealed (and thus DatabaseDriver)
pub struct PostgresDriver { /* ... */ }
impl private::Sealed for PostgresDriver {}
impl DatabaseDriver for PostgresDriver { /* ... */ }
// External code can USE DatabaseDriver but not implement itNon-Exhaustive Enums
Allow adding variants without breaking downstream:
#[non_exhaustive]
pub enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Resize { width: u32, height: u32 },
// Future variants won't break the API
}
// External match MUST have _ arm:
match event {
Event::Click { x, y } => handle_click(x, y),
Event::KeyPress(c) => handle_key(c),
Event::Resize { width, height } => handle_resize(width, height),
_ => {} // Required: future variants
}repr(transparent)
For newtypes that need identical memory layout to the wrapped type (FFI, safety):
#[repr(transparent)]
pub struct NonZeroU32(u32);
// Same size and alignment as u32 — safe to transmute
// Useful for: FFI safety, optimization, zero-overhead wrappersThe Never Type (!)
Use the never type ! to indicate computations that diverge (i.e. never return). It can be coerced to any other type.
fn loop_forever() -> ! {
loop {
// never returns
}
}PhantomData Marker Types
Use std::marker::PhantomData to tell the compiler that a struct behaves as if it owns a value of type T even if it only uses it at compile time (e.g. for lifetime bounds or variance assertions).
use std::marker::PhantomData;
pub struct Serializer<T> {
format: String,
_marker: PhantomData<T>, // type-level association
}
impl<T> Serializer<T> {
pub fn new(format: String) -> Self {
Self {
format,
_marker: PhantomData,
}
}
}Pattern Selection Guide
| Need | Pattern |
| Prevent ID swaps | Newtype |
| Validate at boundary | Newtype + new() returning Result |
| Required builder fields | Typestate Builder |
| State machine (compile-time) | Typestate with PhantomData |
| State machine (runtime) | Enum with transitions |
| Prevent external trait impl | Sealed Trait |
| Future-proof enum | #[non_exhaustive] |
| Generic type info, no data | PhantomData |
Anti-Patterns
// Bad: boolean flags allow invalid combinations and unclear call sites.
fn connect(use_tls: bool, verify_cert: bool) {}
connect(false, true);
// Good: encode modes as named variants.
enum TlsMode { Disabled, InsecureForLocalDev, Verified }
fn connect(tls: TlsMode) {}// Bad: public raw IDs are easy to swap.
fn load(user_id: u64, order_id: u64) {}
// Good: newtypes prevent accidental cross-domain use.
struct UserId(u64);
struct OrderId(u64);
fn load(user_id: UserId, order_id: OrderId) {}Design Checklist
- Use newtypes for domain IDs, units, validated strings, and external identifiers.
- Prefer enums over stringly-typed or boolean state.
- Use typestate only when it prevents real misuse at compile time.
- Keep marker types zero-sized and private unless downstream users need them.
- Document invariants that unsafe code or FFI depends on.
- Add compile-fail examples or tests for APIs whose safety comes from type restrictions.
References
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

