Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,14 @@ There are two independent expansion paths:

For a trait `Foo` with hot types `[A, B]`:
1. Generates hidden inner trait `__FooImpl` with `__spec_*` method declarations.
2. Generates a compile-time assertion that `size_of::<*const dyn Foo>() == 2 * size_of::<usize>()`.
3. Generates `impl<'a> dyn Foo + 'a { ... }` with two primitive helpers — `__devirt_raw_parts(&Self) -> [usize; 2]` and `__devirt_vtable_for::<T: __FooImpl + 'static>() -> usize` — plus inherent methods for each user-declared trait method whose body is the vtable-comparison dispatch shim.
4. Generates public marker trait `Foo: __FooImpl` (no methods of its own).
5. Blanket impl: `impl<T: __FooImpl + ?Sized> Foo for T {}`.
2. Generates public base trait `FooBase` with the original method signatures (including default bodies). Cold types implement this directly without depending on `devirt`.
3. Generates blanket impl `impl<T: FooBase + ?Sized> __FooImpl for T { ... }` bridging base trait methods to `__spec_*` via `#[inline(always)]` delegation.
4. Generates a compile-time assertion that `size_of::<*const dyn Foo>() == 2 * size_of::<usize>()`.
5. Generates `impl<'a> dyn Foo + 'a { ... }` with two primitive helpers — `__devirt_raw_parts(&Self) -> [usize; 2]` and `__devirt_vtable_for::<T: __FooImpl + 'static>() -> usize` — plus inherent methods for each user-declared trait method whose body is the vtable-comparison dispatch shim.
6. Generates public marker trait `Foo: __FooImpl` (no methods of its own).
7. Blanket impl: `impl<T: __FooImpl + ?Sized> Foo for T {}`.

Hot types use `#[devirt]` on impl blocks → generates `impl __FooImpl for T` directly (bypasses the base blanket, enables `#[inline]`). Cold types implement `FooBase` → blanket provides `__FooImpl` → blanket provides `Foo`.

### `__devirt_define! { @impl ... }` Expansion

Expand Down
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use std::f64::consts::PI;
struct Circle { radius: f64 }
struct Rect { w: f64, h: f64 }
struct Triangle { a: f64, b: f64, c: f64 }
struct Hexagon { side: f64 }

// 1. Define trait — list hot types in the attribute
#[devirt::devirt(Circle, Rect)]
Expand All @@ -36,7 +37,7 @@ pub trait Shape {
fn scale(&mut self, factor: f64);
}

// 2. Hot type — vtable-cmp match, direct inlined call
// 2. Hot type — #[devirt] generates optimized direct dispatch
#[devirt::devirt]
impl Shape for Circle {
fn area(&self) -> f64 { PI * self.radius * self.radius }
Expand All @@ -51,7 +52,7 @@ impl Shape for Rect {
fn scale(&mut self, factor: f64) { self.w *= factor; self.h *= factor; }
}

// 3. Cold type — falls back to vtable
// 3a. Cold type via #[devirt] — also works, adds #[inline]
#[devirt::devirt]
impl Shape for Triangle {
fn area(&self) -> f64 {
Expand All @@ -64,7 +65,14 @@ impl Shape for Triangle {
}
}

// 4. Use — completely normal dyn Trait
// 3b. Cold type via ShapeBase — no devirt dependency needed
impl ShapeBase for Hexagon {
fn area(&self) -> f64 { 1.5 * 3f64.sqrt() * self.side * self.side }
fn perimeter(&self) -> f64 { 6.0 * self.side }
fn scale(&mut self, factor: f64) { self.side *= factor; }
}

// 4. Use — completely normal dyn Trait. Both cold types work identically.
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
Expand Down Expand Up @@ -102,6 +110,24 @@ devirt::devirt! {

Both APIs produce identical expanded code.

### Implementing from downstream crates

The trait definition generates a public `{Trait}Base` trait (e.g.,
`ShapeBase`) with the original method signatures. Downstream crates
can implement this trait directly — no `devirt` dependency required:

```rust
// In a downstream crate — no devirt in Cargo.toml
impl my_shapes::ShapeBase for MyWidget {
fn area(&self) -> f64 { self.w * self.h }
fn perimeter(&self) -> f64 { 2.0 * (self.w + self.h) }
fn scale(&mut self, factor: f64) { self.w *= factor; self.h *= factor; }
}

// MyWidget now satisfies the Shape bound:
let shapes: Vec<Box<dyn my_shapes::Shape>> = vec![Box::new(MyWidget { w: 3.0, h: 4.0 })];
```

### `dyn Trait + Send` / `Sync`

The proc-macro attribute automatically emits dispatch shims for
Expand Down
7 changes: 4 additions & 3 deletions crates/core/examples/shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//!
//! Note: this example uses `std`; the `devirt` crate itself is `#![no_std]`.
#![expect(clippy::print_stdout, reason = "example intentionally prints output to demonstrate API usage")]
#![expect(clippy::unnecessary_literal_bound, reason = "trait declares &str, not &'static str")]

struct Circle { radius: f64 }
struct Rect { w: f64, h: f64 }
Expand Down Expand Up @@ -97,9 +98,9 @@ impl Shape for Triangle {
fn name(&self) -> &str { "triangle" }
}

// Downstream type — not in the hot list, automatically uses vtable
#[devirt::devirt]
impl Shape for Hexagon {
// Cold type — implements ShapeBase directly, no #[devirt] needed.
// Downstream crates can do this without depending on devirt at all.
impl ShapeBase for Hexagon {
fn area(&self) -> f64 { 1.5 * 3.0_f64.sqrt() * self.side * self.side }
fn perimeter(&self) -> f64 { 6.0 * self.side }
fn scale(&mut self, factor: f64) { self.side *= factor; }
Expand Down
Loading