Skip to main content

Crate devirt

Crate devirt 

Source
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 __XImpl with __spec_* methods and __try_*_as_* witness methods (one default per hot type per method)
  • A public trait X with 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.

Macros§

impl
Implements a devirtualized trait for a concrete type.
trait
Declares a trait with transparent devirtualization.