Expand description
Transparent devirtualization for Rust trait objects, with #![no_std] support.
Hot types get witness-method dispatch: a thin inlined check routes directly
to the concrete type’s method, avoiding the vtable.
Cold types fall back to normal vtable dispatch.
Callers use plain dyn Trait — no wrappers, no special calls.
§Architecture
r#trait! generates:
- A hidden inner trait
__XImplwith__spec_*methods and__try_*_as_*witness methods (one default per hot type per method) - A public trait
Xwith devirtualized default methods - A blanket impl:
impl<T: __XImpl> X for T {}
r#impl! generates:
impl __XImpl for ConcreteType { ... }with the__spec_*bodies- When marked
[hot], also overrides the matching witness methods so dispatch bypasses the vtable for this type
§Usage
devirt::r#trait! {
pub MyTrait [HotType1, HotType2] {
/// Doc for the method.
fn method(&self) -> ReturnType;
}
}
// Hot type: witness override — one direct call, no vtable
devirt::r#impl!(MyTrait for HotType1 [hot] {
fn method(&self) -> ReturnType { ... }
});
// Cold type: falls back to vtable
devirt::r#impl!(MyTrait for ColdType {
fn method(&self) -> ReturnType { ... }
});§Required: enable LTO
This crate relies on cross-function inlining to eliminate dispatch overhead.
Without LTO, witness methods may not inline and performance will be worse
than plain dyn Trait.
[profile.release]
lto = "thin"
codegen-units = 1§Performance characteristics
With LTO enabled, hot-type dispatch compiles to the same machine code as a
direct method call — zero overhead vs plain dyn Trait. Cold types pay a
small penalty: each hot type adds one inlined branch (returning None) to
the cold path before the vtable fallback. This means cold-path overhead
grows linearly with the number of hot types.
The crate is most effective when hot types dominate the population (80%+ of trait objects). In mixed collections with many cold types, the cold-path penalty can outweigh the hot-path gains.
§Limitations
Object safety required. All trait methods must be object-safe — no
generic parameters, no Self in return position. The generated blanket
impl requires dyn Trait to work, so violating object safety will produce
errors pointing at the generated impl rather than the method definition.
Snake-case name collisions. Hot type names are converted to snake_case
(via paste’s :snake) for witness method names. Two types that produce
the same snake_case form (e.g., HTTPClient and HttpClient both become
http_client) will generate conflicting method names. Use distinct type
names when this would be ambiguous.