agentleFS
dawidpereiradawidpereira/rust-skills/skills/rust-types/SKILL.md

rust-types

Skill0 starsChanged 8 months agoMITView on GitHub

Rust type system design — generics, traits, dispatch, newtypes, typestate, PhantomData. Use when designing type-safe APIs, encountering E0277/E0308/E0599/E0038, choosing between static and dynamic dispatch, or implementing sealed traits and parse-don't-validate patterns. Also use when working with generic bounds, trait objects, newtype wrappers, or making invalid states unrepresentable through the type system.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every time you reach for a String where an enum would work, a u64 where a UserId would be clearer, or a runtime check where a compile-time …

    line 19

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Type System Design

Core Question

What invariants should the type system enforce?

Every time you reach for a String where an enum would work, a u64 where a UserId would be clearer, or a runtime check where a compile-time guarantee is possible — ask whether the type system can do the work for you.

The Rust type system is powerful enough to catch entire categories of bugs at compile time. The question is always: is the added type complexity worth the guarantee?


Error → Design Question

ErrorDon't Just SayAsk Instead
E0277 (trait not satisfied)"Add the bound"Is the trait design right for this use case?
E0308 (type mismatch)"Cast it"Are the types modeling the domain correctly?
E0599 (method not found)"Wrong type"Should this method exist on this type?
E0038 (not object-safe)"Box it"Should this use static dispatch instead?

Quick Decisions

SituationReach ForWhy
Prevent mixing IDs (user_id vs order_id)Newtype wrapperCompile-time distinction, zero cost
Value must be validated (email, port)Parse-don't-validate newtypeValidity guaranteed by construction
Mutually exclusive statesEnumInvalid states unrepresentable
Optional valueOption<T>Absence is explicit
Fallible operationResult<T, E>Failure is explicit
State machine transitionsTypestate patternInvalid transitions won't compile
Prevent external trait implementationsSealed traitEvolution without breaking changes
Add methods to foreign typesExtension traitWorks around orphan rules
Type-level marker, no runtime dataPhantomData<T>Zero-size, expresses type relationship
Fixed set of valid values (not strings)Enum, not &strTypos caught at compile time
Heterogeneous collectionBox<dyn Trait>Different concrete types, same interface
Monomorphized hot pathimpl Trait / genericsZero-cost, no vtable dispatch
Callback parameterimpl Fn() boundMost flexible for callers, zero-cost
Mutable callbackimpl FnMut() boundWhen the callback needs to mutate captured state
One-shot callback (ownership)impl FnOnce() boundCallback consumes captured values
Closures that cross threadsmove + Send + 'staticForces value capture for thread safety
Storing mixed closuresBox<dyn Fn()>Type erasure when closure types vary
Custom collection iterationimplement Iterator + IntoIteratorEnables for loops and adapter chains
Lazy transformation pipelineiterator adapters (map, filter, flat_map)Zero-allocation chains until collect

Static vs Dynamic Dispatch

Need different concrete types in the same collection?
├── Yes → dyn Trait (dynamic dispatch)
│   └── Box<dyn Trait> for owned, &dyn Trait for borrowed
└── No → impl Trait or generics (static dispatch)
    └── Monomorphized: zero overhead, larger binary
Static (impl Trait / generics)Dynamic (dyn Trait)
PerformanceInlined, zero overheadVtable lookup per call
Binary sizeLarger (monomorphized copies)Smaller (single implementation)
Heterogeneous collectionsNoYes
Compile timeSlowerFaster
Object safety requiredNoYes

Object safety requires: no Self: Sized bound, no generic methods, no Self in return position (without -> Box<dyn Trait>).


Newtype Pattern

Zero-cost wrapper that adds type safety:

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OrderId(u64);

// These are now distinct types — can't mix them
fn get_order(user: UserId, order: OrderId) -> Order { ... }

Parse-Don't-Validate

Validate at construction, trust the type afterward:

pub struct Email(String);

impl Email {
    pub fn new(s: &str) -> Result<Self, EmailError> {
        if s.contains('@') && s.len() > 3 {
            Ok(Email(s.to_string()))
        } else {
            Err(EmailError::Invalid(s.to_string()))
        }
    }

    pub fn as_str(&self) -> &str { &self.0 }
}

// Once you have an Email, it's valid — no re-checking needed
fn send(to: &Email, body: &str) { ... }

Usage Scenarios

Scenario 1: "I have a function taking two u64 parameters and callers keep swapping them" → Wrap each in a newtype: UserId(u64) and OrderId(u64). Zero runtime cost, compiler catches swaps.

Scenario 2: "Should I use Box<dyn Handler> or generics for my plugin system?" → If you need a Vec<Box<dyn Handler>> with different concrete types, use dynamic dispatch. If each call site uses one known type, use impl Handler for zero-cost dispatch.

Scenario 3: "I'm getting E0277 'the trait bound is not satisfied' everywhere" → Don't just add bounds. Check: is the bound on the right level (impl block vs struct)? Are you constraining too early? Bounds on the struct definition propagate everywhere — prefer bounds on impl blocks.


Reference Files

FileRead When
references/generics.mdWorking with generic bounds, Into/AsRef/From patterns, avoiding over-abstraction
references/type-driven.mdDesigning newtypes, enums for states, PhantomData, parse-don't-validate, typestate
references/trait-design.mdSealed traits, extension traits, object safety, common trait implementations
references/closures-and-iterators.mdFn/FnMut/FnOnce traits, capture semantics, move closures, returning closures, custom Iterator/IntoIterator, FromIterator

Cross-References

WhenCheck
Ownership issues with generic typesrust-ownership → Quick Decisions
Error types and Result patternsrust-errors → Quick Decisions
API design beyond type system (builders, naming)rust-api → Quick Decisions
FFI newtypes with #[repr(transparent)]rust-unsafe → Quick Decisions
Move closures for spawn, Send boundsrust-async → Quick Decisions
Iterator chains, lazy evaluationrust-perf → Quick Decisions

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.