enum-typer
Type-indexed enums, pattern matching and GADTs for Rust
A procedural macro library that brings Generalized Algebraic Data Types (GADTs) to Rust through a powerful type_enum! macro. Define enums where each variant can refine the overall type, enabling compile-time type safety for complex data structures like type-safe expression trees, length-indexed vectors, and state machines.
Features
- 🎯 Type Indexing - Each variant can specify different type parameters, like GADTs in Haskell/OCaml
- 🔒 Phantom Types - Track compile-time state (empty/non-empty lists, type-level naturals)
- 📦 Trait Objects - Automatic trait generation with
Box<dyn Trait>support for existential types - 🔄 Pattern Matching - Runtime type-based matching with
match_t!macro - 🎨 Methods - Define methods directly in the enum with type-indexed return types
- âš¡ Smart Inference - Automatic type parameter filtering and PhantomData injection
Quick Start
Add to your Cargo.toml:
[]
= "0.1.0"
Basic Example
use type_enum;
type_enum!
// Use as trait objects
let value: = Boxnew;
Type Indexing (GADTs)
Define enums where variants constrain the overall type parameter:
type_enum!
;
;
type EitherRef<A, B, Tag> = ;
// Type system proves this value is Left
let value: = Boxnew;
// Can't compile - type mismatch!
// let wrong: EitherRef<i32, String, LeftTag> = Box::new(Right("hello".to_string()));
Each variant acts as a type-level proof of which case you have. The Tag parameter is refined by the variant constructor.
Phantom Types for Compile-Time Safety
Track state at the type level to prevent runtime errors:
;
;
type_enum!
type SafeListRef<T, E> = ;
// This function only accepts non-empty lists
let list: = Boxnew;
let head = safe_head; // ✓ Compiles
// safe_head(Box::new(Nil)); // ✗ Compile error!
Pattern Matching with match_t!
Runtime pattern matching on trait objects:
type_enum!
type SumRef<A, B> = ;
let val = Boxnew;
let result = fold_sum;
assert_eq!;
Methods and Existential Returns
Define methods that return type-indexed results. The return type T is existentially quantified - it depends on which variant you have:
type_enum!
type ArithRef<T> = ;
let expr: = Boxnew;
assert_eq!; // Returns i32
let bool_expr: = Boxnew;
assert_eq!; // Returns bool
The type system ensures you can't mix incompatible types - Add only accepts Arith<i32>, not Arith<bool>. The return type of eval() changes based on the type index T.
How It Works
The type_enum! macro transforms your enum definition into:
- A trait with the enum's name containing any defined methods
- Separate structs for each variant
- Trait implementations with smart generic filtering to avoid unconstrained type parameters
- Automatic PhantomData injection for phantom type parameters
Example Transformation
type_enum!
Expands approximately to:
;
;
Notice how Nil only has impl<T>, not impl<T, E> - the macro automatically filters unused type parameters.
Advanced Features
Trait Bounds Preservation
Type parameter bounds are automatically preserved:
type_enum!
// Generated impl preserves N: Nat bound
// impl<T: 'static, N: Nat + 'static> SafeVector<T, Succ<N>> for VCons<T, N> { ... }
Limitations
- Inference limits: Associated types like
N::Predmay require explicit type annotations - No variant with type parameters: Variants cannot have their own type parameters
- 'static bound: All type parameters require
'staticfor trait object compatibility - No exhaustiveness:
match_t!panics on unmatched patterns (no compile-time exhaustiveness checking)
Examples
See the tests/examples.rs file for more examples including:
- Type-safe arithmetic expression trees
- Empty/non-empty lists with compile-time guarantees
- Length-indexed vectors with type-level naturals
- Sum types with generic folding
License
MIT OR Apache-2.0