| Crates.io | tola-caps |
| lib.rs | tola-caps |
| version | 0.2.0 |
| created_at | 2026-01-04 23:25:47.11439+00 |
| updated_at | 2026-01-11 06:47:13.290027+00 |
| description | Capability system enabling type-level state tracking, trait detection, and stable specialization. |
| homepage | |
| repository | https://github.com/tola-rs/tola-caps |
| max_upload_size | |
| id | 2022754 |
| size | 346,874 |
Type-level capability system for Rust
Let the compiler track your state.
tola-caps is a typestate library: encode state into types, let the compiler verify transitions. Each capability is a zero-sized marker type, and a capability set is a type-level collection of these markers.
Type -> Hash -> Point in metric space -> Position in set
You define states (Parsed, Validated), the compiler enforces correct ordering. Zero runtime cost.
&, |, !, () support(A & !B) instead of complex generics[dependencies]
tola-caps = "0.2" # default: std + specialize + detect
# Or customize:
tola-caps = { version = "0.2", default-features = false, features = ["alloc"] }
| Feature | Description |
|---|---|
std (default) |
Standard library support, includes alloc |
alloc |
Alloc types (Vec, Box, String...) without full std |
specialize (default) |
Enable #[specialize] and specialization! macros |
detect (default) |
Std trait detection (caps_check!, AutoCaps) |
| Macro | Target | Purpose |
|---|---|---|
#[derive(Capability)] |
struct | Define capability marker |
#[derive(AutoCaps)] |
struct/enum | Enable type for generic context detection |
#[trait_autocaps] |
trait | Enable trait for generic context detection |
#[caps_bound] |
fn/impl | Add capability constraints |
#[specialize] |
impl | Attribute-style specialization |
specialization!{} |
- | Block-style specialization syntax |
caps![] |
- | Build capability set type |
caps_check! |
- | Compile-time trait detection |
Note:
caps_check! for concrete types works with ANY trait directlyfn<T>) need T: AutoCaps for both caps_check! and specialization!AutoCaps#[derive(AutoCaps)] enables custom types for generic detection and specialization#[trait_autocaps] enables custom traits for detection/specializationuse tola_caps::prelude::*;
// 1. Define your states as capabilities
#[derive(Capability)] struct Parsed;
#[derive(Capability)] struct Validated;
#[derive(Capability)] struct Signed;
// 2. Use PhantomData to track capabilities at type level
pub struct Doc<C = caps![]> {
content: String,
_caps: std::marker::PhantomData<C>,
}
impl Doc {
pub fn new(s: &str) -> Self {
Doc { content: s.to_string(), _caps: std::marker::PhantomData }
}
}
// 3. Add capability constraints to functions
fn parse(doc: Doc) -> Doc<caps![Parsed]> {
Doc { content: doc.content, _caps: std::marker::PhantomData }
}
#[caps_bound(C: Parsed, with(Validated))]
fn validate<C>(doc: Doc<C>) -> Doc<with![C, Validated]> {
Doc { content: doc.content, _caps: std::marker::PhantomData }
}
#[caps_bound(C: Parsed & Validated, with(Signed))]
fn sign<C>(doc: Doc<C>) -> Doc<with![C, Signed]> {
Doc { content: doc.content, _caps: std::marker::PhantomData }
}
fn main() {
let doc = Doc::new("hello");
let doc = parse(doc);
let doc = validate(doc); // OK: Has Parsed
let doc = sign(doc); // OK: Has Parsed & Validated
// sign(parse(Doc::new("x"))); // ERROR: Compile error: missing Validated
}
Check if a type implements any trait at compile time:
use tola_caps::caps_check;
use std::fmt::Debug;
// Concrete types - works with ANY trait, no setup needed
assert!(caps_check!(String: Clone));
assert!(!caps_check!(String: Copy));
assert!(caps_check!(String: Clone & !Copy));
// Custom traits also work on concrete types
trait MyTrait {}
impl MyTrait for String {}
assert!(caps_check!(String: MyTrait));
For generic type parameters, add AutoCaps bound:
use tola_caps::{caps_check, detect::AutoCaps};
fn pick_strategy<T: AutoCaps>() -> &'static str {
if caps_check!(T: Copy) { "memcpy" }
else if caps_check!(T: Clone) { "clone" }
else { "move" }
}
Why AutoCaps is needed for generics:
String): The compiler knows all trait impls at the call siteT: The compiler doesn't know T's traits until instantiationAutoCaps provides a type-level capability set that enables detection in generic contextsAutoCaps automatically#[derive(AutoCaps)]
Or use specialization for true type-level dispatch:
```rust
use tola_caps::specialization;
specialization! {
trait Strategy {
fn pick() -> &'static str;
}
impl<T> Strategy for T {
default fn pick() -> &'static str { "move" }
}
impl<T: Clone> Strategy for T {
default fn pick() -> &'static str { "clone" }
}
impl<T: Copy> Strategy for T {
fn pick() -> &'static str { "memcpy" }
}
}
tola-caps provides stable Rust specialization using two syntax styles. Both achieve the same goal: select different implementations based on trait bounds.
How it works: The specialization! macro transforms trait bounds into capability queries on Cap<T> (the type's capability set). Selection happens at type-level via SelectCap<Condition, Then, Else>. This requires T: AutoCaps to provide the capability set.
Key insight: T: Clone becomes Cap<T>: Evaluate<IsClone> - dispatching based on whether IsClone exists in T's capability set.
specialization!All-in-one macro with default keyword for overridable items:
use tola_caps::specialization;
specialization! {
trait Encode {
fn encode(&self) -> Vec<u8>;
}
impl<T> Encode for T {
default fn encode(&self) -> Vec<u8> { vec![] } // fallback
}
impl<T: Clone> Encode for T {
default fn encode(&self) -> Vec<u8> { vec![0xC1] } // Clone impl
}
impl<T: Copy> Encode for T {
fn encode(&self) -> Vec<u8> { vec![0xC0] } // Copy impl (most specific)
}
impl Encode for String {
fn encode(&self) -> Vec<u8> { self.as_bytes().to_vec() } // concrete override
}
}
// Usage:
let v: Vec<u8> = 42i32.encode(); // picks Copy impl
let v: Vec<u8> = "hello".to_string().encode(); // picks String impl
#[specialize]Distributed across files, with explicit constraint syntax:
use tola_caps::specialize;
trait Encode { fn encode(&self) -> Vec<u8>; }
#[specialize(default)] // Fallback impl
impl<T> Encode for T {
fn encode(&self) -> Vec<u8> { vec![] }
}
#[specialize(T: Clone)] // When T: Clone
impl<T> Encode for T {
fn encode(&self) -> Vec<u8> { vec![1] }
}
#[specialize(T: Clone, U: Copy)] // Multiple bounds
impl<T, U> Pair<T, U> for (T, U) { /* ... */ }
#[specialize] // Concrete type (highest priority)
impl Encode for String {
fn encode(&self) -> Vec<u8> { self.as_bytes().to_vec() }
}
Resolution order (highest to lowest priority):
impl Encode for Stringimpl<T: Clone + Copy> Encode for Timpl<T: Clone> Encode for Timpl<T> Encode for T with default keywordtola-caps uses type-level capability dispatch (different from Rust's nightly specialization):
Cap<T> capability set lookup, not trait impl orderingAutoCaps on types, but more reliable on stable RustFor detailed analysis and caveats, see the Specialization Soundness section.
AutoCaps required for generic dispatch:
caps_check! AND specialization! use Cap<T> internallycaps_check!(String: Clone) works directly (no generics)T: Needs T: AutoCaps for Cap<T> to existAutoCaps// Concrete types: no AutoCaps needed
assert!(caps_check!(String: Clone)); // OK
// Generic context: AutoCaps required for Cap<T>
fn strategy<T: AutoCaps>() -> &'static str {
if caps_check!(T: Clone) { "clone" } else { "move" }
}
// Custom type for generic specialization
#[derive(Clone, AutoCaps)]
struct MyType;
assert_eq!(strategy::<MyType>(), "clone");
#[trait_autocaps] for generic dispatch
#[trait_autocaps]
trait MyTrait { } // Generates IsMyTrait capability marker
#[derive(Capability)] struct TxStarted;
#[derive(Capability)] struct TxCommitted;
#[caps_bound(C: !TxStarted, with(TxStarted))]
impl<C> Conn<C> {
fn begin(self) -> Conn<with![C, TxStarted]> { todo!() }
}
#[caps_bound(C: TxStarted & !TxCommitted, with(TxCommitted))]
impl<C> Conn<C> {
fn commit(self) -> Conn<with![C, TxCommitted]> { todo!() }
}
#[derive(Capability)] struct Admin;
#[caps_bound(C: Admin, without(Admin))]
fn drop_admin<C>(user: User<C>) -> User<without![C, Admin]> {
todo!()
}
type Combined = union![caps![A], caps![B]];
type Common = intersect![caps![A, B], caps![B, C]];
The Challenge: Type-level set membership when Rust's coherence rules prevent arbitrary type comparison.
The Solution: Hash-based routing + compile-time identity verification.
Capability Name -> FNV-1a Hash -> Trie Path -> Identity Check
"Parsed" -> 0x84... -> [8][4]... -> Unique
Each capability gets a deterministic address in a 16-ary trie. Membership is O(1) path lookup.
FNV-1a (64-bit) hashes the capability name into a HashStream. Each nibble (4 bits) selects a slot in the 16-ary Node16 trie.
Hash collisions are unavoidable with 64-bit FNV-1a. tola-caps uses a two-tier identity system:
Tier 1: 64-bit Hash Routing
Tier 2: Full Path Identity (Guaranteed Uniqueness)
name@module::path:file:line:col// Example identity type structure
type Identity = IList64<
IList<C<'c'>, IList<C<'o'>, IList<C<'r'>, ...>>>
>;
Collision Handling: When two capabilities hash to the same trie slot:
This ensures O(1) routing with 100% collision resolution.
Rust prefers inherent methods over trait methods:
trait Fallback { const IS_CLONE: bool = false; }
impl<T> Fallback for T {}
struct Detect<T>(PhantomData<T>);
impl<T: Clone> Detect<T> {
const IS_CLONE: bool = true; // Inherent shadows trait
}
Detect::<String>::IS_CLONE finds the inherent const if String: Clone, else falls back to trait default.
Specialization uses Cap<T> (the type's capability set) for type-level selection:
// Generated by specialization! macro:
type Selected = <Cap<T> as SelectCap<IsClone, CloneImpl, DefaultImpl>>::Out;
// SelectCap checks if IsClone exists in Cap<T>:
// - If T: Clone, IsClone is Present -> selects CloneImpl
// - Otherwise, IsClone is Absent -> selects DefaultImpl
This is pure type-level computation - no runtime dispatch.
| Operation | Cost |
|---|---|
| Routing | O(1) - fixed 16 levels |
| Collision resolution | O(1) in practice |
| Runtime | Zero - compile-time only |
| Memory | Zero - all PhantomData |
Human-readable errors - shows your capability logic, not compiler internals:
error[E0277]: Capability requirement failed: ((A | B) & !C)
--> src/main.rs:44:19
|
44 | complex_logic(doc_c);
| ^^^^^^^^^^^^^ requirement '((A | B) & !C)' not satisfied
|
= help: ensure the type has capabilities: (A OR B) AND NOT C
tola-caps uses type-level capability dispatch, which differs fundamentally from Rust's nightly specialization feature:
Mechanism comparison:
| Aspect | Nightly specialization |
tola-caps |
|---|---|---|
| Dispatch | Trait impl ordering | Cap<T> capability set lookup |
| Decision time | Monomorphization | Type-level (compile-time) |
| Lifetime handling | Problematic (unsound) | Irrelevant (no lifetime dispatch) |
| Associated types | Can be unsound | Not affected |
How tola-caps avoids nightly's unsoundness:
No lifetime-dependent dispatch: Selection is based on capability markers (IsClone, IsCopy), not on lifetime bounds like 'static. The SOUNDNESS.md documents that lifetime-dependent specialization is the primary source of unsoundness in nightly.
No associated type normalization issues: tola-caps generates separate impl structs and selects between them at type-level. There's no "which impl to use" decision that could differ between type-checking and codegen.
Capability set is fixed per type: Cap<MyType> is deterministic - it's built from #[derive(AutoCaps)] at definition time, not inferred from context.
What works safely:
// OK: Trait bound specialization
impl<T: Clone> Trait for T { } // IsClone in Cap<T>
impl<T> Trait for T { } // Fallback
// OK: Concrete type override
impl Trait for String { } // Highest priority
What is NOT supported (by design):
// NOT supported: Lifetime-dependent dispatch
// tola-caps cannot distinguish T: 'static from T
impl<T: 'static> Trait for T { }
impl<T> Trait for T { }
Caveats:
'static or other lifetime bounds#[trait_autocaps] for generic dispatchAutoCaps is the foundation:
caps_check! and specialization! use Cap<T> for dispatchAutoCaps#[derive(AutoCaps)] for generic contexts#[derive(AutoCaps)]
struct MyType; // Now works with caps_check! and specialization!
#[trait_autocaps] for custom traits:
caps_check!(T: MyTrait) in generic contextsMyTrait bound#[trait_autocaps]
trait MyTrait { } // Now IsMyTrait capability is available
MIT