typed_use_cases
Formalize use cases at the type level. Zero runtime overhead.
A Rust library that brings UML use cases into your type system, providing compile-time awareness when your code structure changes in ways that affect declared use cases.
⚠️ Experimental: This is a proof-of-concept library. It has not been validated in production environments and should be considered an exploratory tool for representing use cases in type systems.
The Problem
In traditional development, use cases live outside the code:
- Maintained manually: Use case diagrams and specifications are separate documents
- Silent drift: When code changes, no one remembers to update the use cases
- Becomes stale: Documentation diverges from implementation over time
- Poorly defined: Use cases often end up ambiguous, generic, or unmeasurable
- Abandoned: Teams stop using use cases because they're too hard to maintain
The fundamental issue: use cases and code evolve independently with no connection between them.
What This Library Does
typed_use_cases brings use cases into your type system with zero runtime cost:
- Formalizes use cases as named traits in Rust
- Compile-time awareness: The compiler knows which use cases exist
- Breaking changes are visible: If you change a method signature or remove an implementation, the compiler breaks at the use case level
- No runtime overhead: Uses a zero-sized System type (0 bytes)
- Verification in tests only: Uses
#[cfg(test)]to verify implementations
What This Library Is NOT
This library is not:
- ❌ NOT a verification tool - It does not prove your program satisfies a use case
- ❌ NOT a testing framework - It does not test that your system fulfills use case requirements (this is not feasible to automate)
- ❌ NOT a formal methods tool - It does not prove program properties
- ❌ NOT for runtime enforcement - All verification happens at compile time, in test builds only
What This Library IS
This library is:
- ✅ A type-level representation - Makes your type system aware of declared use cases
- ✅ A compile-time alarm - Alerts you when changes might violate use case contracts
- ✅ A documentation tool - Use cases live in code, not separate documents
- ✅ A proof of concept - An experiment to see if type-level use cases provide value
You can think of it as: "The compiler knows which use cases should exist, and will complain if you break their structure."
It does not guarantee that your implementation is correct or complete, only that the structure exists.
Possible Use Cases for This Library
1. LLM Context Enhancement
This library may be useful for Large Language Models (LLMs) to:
- Parse use case declarations directly from code
- Use use cases as a source of truth when generating or modifying code
- Better understand system intent and behavior contracts
2. Documentation Generation
Use case metadata (NAME, DESCRIPTION) is accessible at compile time and can be:
- Extracted for automatic documentation
- Used to generate diagrams
- Displayed in developer tools
3. Architectural Awareness
During refactoring or feature development:
- The type system alerts you when changes affect use cases
- Use case traits act as stable interfaces
- Easier to see which parts of the system implement which business logic
The Solution
typed_use_cases makes use cases first-class citizens in your type system:
use ;
// 1. Declare actors and entities
// 2. Declare use cases as named traits
// 3. Define your System type (application-specific, not part of the library)
; // Zero-sized type
// 4. Implement use cases on your System
// 5. Verify at compile time (in tests) that all use cases are implemented
implement_all_use_cases!;
Result: The type system is aware of your use cases. If you change a signature or remove an implementation, compilation fails in tests.
Important: The System type is defined by you, not by this library. It represents your application's use case implementations.
How It Works
Zero-Sized Type (ZST)
System is a zero-sized type — it occupies 0 bytes at runtime:
assert_eq!;
The System type exists purely for the compiler. It acts as a witness that your use cases are implemented.
Compile-Time Verification
The implement_all_use_cases! macro expands only in test builds:
This generates zero runtime code. The assertions run at compile time during cargo test.
No Controller Changes
Your HTTP handlers and controllers remain unchanged. The library is purely a compile-time concern:
// Your controller code - unchanged
async
The System type and trait implementations serve as documentation and verification, not as runtime infrastructure.
Install
Using cargo (recommended):
Or add to your Cargo.toml:
[]
= "0.1"
That's it! No additional dependencies needed.
Note: static_assertions is only needed for the compile-time verification macro and does not appear in release builds.
Concepts
Actor
An actor is the initiator of a use case. Actors exist independently of any entity or action.
;
Entity
An entity is what a use case operates on — the subject of the action.
DependentEntity
A dependent entity is an entity whose existence is tied to a specific actor (e.g., a Cart belongs to an Authenticated user).
The #[derive(Entity)] macro automatically implements DependentEntity<A> if:
- A field named
ownerexists - Its type is a single-segment path (e.g.,
Authenticated)
Manual implementation:
UseCase
A use case IS the action. It's declared as a named trait that extends UseCase<Actor, Entity> and fixes all type parameters:
Each use case has compile-time metadata:
System
A zero-sized type defined by the user (not by the library) that implements all use cases. It exists only for the compiler and occupies 0 bytes at runtime:
; // Your application defines this
Important: The System type belongs to your application, not to the typed_use_cases library. Each application defines its own System type to implement its use cases.
implement_all_use_cases!
A macro that verifies (at compile time, in test builds only) that System implements every declared use case:
implement_all_use_cases!;
If any use case is missing, compilation fails during cargo test.
Example: E-Commerce
See examples/ecommerce.rs for a complete working example with:
- 3 actors:
Anonymous,Registered,Authenticated - 4 entities:
Catalog,Product,Cart,Order - 3 use cases:
BrowseCatalog,AddItemToCart,Checkout
Run it with:
Design Principles
- Zero runtime overhead:
Systemis a ZST. No allocation, no dynamic dispatch. - Compile-time verification: Use cases are checked at compile time, not runtime.
- No framework lock-in: Compatible with any web framework, DI container, or async runtime.
- No boilerplate in production: Controllers remain unchanged. This is purely additive.
- Stable Rust: No nightly features required.
FAQ
Does this work with async?
The UseCase::execute signature is synchronous by default. For async use cases:
- Use the trait as a contract witness only (for compile-time verification)
- Implement your actual async logic separately
- Or wrap the execution in an async block
Does this impose a dependency injection pattern?
No. Dependencies is a free associated type. Use () if you have no dependencies, or pass in any type you want (a struct, a trait object, a tuple of services, etc.).
What about multiple use cases with the same Actor + Entity?
Each UseCase<A, E> implementation must be unique. If you have two use cases with the same actor and entity types, use different entity types (e.g., Cart vs Order) or create wrapper types.
Does static_assertions appear in my release binary?
No. The implement_all_use_cases! macro expands only under #[cfg(test)], and static_assertions is listed as a # No additional dependencies needed!.
License
MIT
Contributing
Contributions welcome! Please open an issue or pull request on GitHub.