Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
169 changes: 104 additions & 65 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub use paste::paste as __paste;
macro_rules! __devirt_define {
(@trait [$($unsafety:tt)*]
$(#[$meta:meta])*
$vis:vis $trait_name:ident [$($hot:ty),+ $(,)?] {
$vis:vis $trait_name:ident $base_name:ident [$($hot:ty),+ $(,)?] {
$($methods:tt)*
}
) => {
Expand All @@ -146,58 +146,44 @@ macro_rules! __devirt_define {
$vis $($unsafety)* trait [<__ $trait_name Impl>] {
$crate::__devirt_define!{@spec_decl $($methods)*}
}
}

/// Base implementation trait: implement this for cold types
/// without depending on `devirt`.
$vis $($unsafety)* trait $base_name {
$($methods)*
}

$crate::__paste! {
$($unsafety)* impl<__DevirtT: $base_name + ?Sized> [<__ $trait_name Impl>] for __DevirtT {
$crate::__devirt_define!{@blanket_bridge $trait_name, $($methods)*}
}
}

// Compile-time sanity check: `*const dyn Trait` must be a fat
// pointer of exactly two `usize`s. If a future Rust edition
// changes this, compilation fails loudly rather than producing
// UB at runtime.
const _: () = assert!(
::core::mem::size_of::<*const dyn $trait_name>()
== 2 * ::core::mem::size_of::<usize>()
);

// Inherent helpers on `dyn $trait_name` that expose the fat
// pointer's `(data, vtable)` halves and the compiler-assigned
// vtable address for a concrete hot type. These are `#[inline(
// always)]` so LTO folds them into the dispatch shim.
const _: () = assert!(
::core::mem::size_of::<*const dyn $trait_name>()
== 2 * ::core::mem::size_of::<usize>()
);

$crate::__paste! {
impl<'__devirt> dyn $trait_name + '__devirt {
/// Split a fat pointer into `[data, vtable]`.
#[doc(hidden)]
#[inline(always)]
pub fn __devirt_raw_parts(this: &Self) -> [usize; 2] {
// SAFETY: `&(dyn $trait_name + '_)` is a two-`usize`
// fat pointer (verified by the compile-time
// `size_of` assertion above) laid out as
// `[data, vtable]`. Transmuting to `[usize; 2]`
// only reinterprets bits — the data half is still
// borrowed for the duration of `this`, so the
// result may not outlive the borrow.
unsafe { ::core::mem::transmute::<
&Self, [usize; 2],
>(this) }
}

/// Vtable pointer for the `(T, Self)` pair.
#[doc(hidden)]
#[inline(always)]
pub fn __devirt_vtable_for<
__DevirtT: [<__ $trait_name Impl>] + 'static,
>() -> usize {
// A dangling, non-null, aligned `*const __DevirtT`.
// We never dereference it — the coercion below only
// reads the vtable metadata the compiler attaches.
let fake: *const __DevirtT = ::core::ptr::without_provenance(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of ::core::ptr::without_provenance was stabilized in Rust 1.76. If this crate intends to support older Rust versions (MSRV), this will be a breaking change. However, given the use of other modern features like the expect attribute (stabilized in 1.81 for some uses), this is likely acceptable. If a lower MSRV is required, consider using addr as *const T which is the older way to create a pointer from an address.

::core::mem::align_of::<__DevirtT>(),
);
// Coercion is a metadata-attaching op; the resulting
// fat pointer's vtable half is the
// `(__DevirtT, $trait_name)` vtable selected by the
// compiler. Its data half is `fake`, which we discard.
let fat: *const Self = fake;
// SAFETY: `*const Self` (dyn trait fat pointer) is
// two `usize`s by the compile-time assertion above.
// We read only the vtable half; the dangling data
// half is discarded without dereferencing.
let __parts: [usize; 2] = unsafe {
::core::mem::transmute::<
*const Self, [usize; 2],
Expand All @@ -207,14 +193,6 @@ macro_rules! __devirt_define {
}
}

// Inherent dispatch methods on `dyn $trait_name`. These
// contain the vtable-comparison hot-path and take priority
// over the trait's default methods during method resolution,
// so a call like `dyn_trait.method()` reaches this block
// before falling back to the trait method. Putting the cast
// `self as *const dyn $trait_name` here (where `Self = dyn
// $trait_name`) avoids the `Self: Sized` requirement that
// would otherwise arise in a default method body.
impl<'__devirt> dyn $trait_name + '__devirt {
$crate::__devirt_define!{
@inherent_decl
Expand All @@ -225,18 +203,6 @@ macro_rules! __devirt_define {
}
}

// The public trait is a thin marker over the hidden inner
// trait: it carries no methods of its own, so `dyn
// $trait_name` has no trait methods to conflict with the
// inherent dispatch methods emitted above. Methods named
// from the user's declaration resolve unambiguously to the
// inherent block.
//
// Concrete-type callers that want to bypass dispatch
// entirely can either call `<$trait_name>::$method` via
// an explicit dyn coercion `(&t as &dyn $trait_name).$method
// (...)` or call `<T as __${trait_name}Impl>::__spec_$method
// (&t, ...)` via UFCS.
$(#[$meta])*
$vis $($unsafety)* trait $trait_name: [<__ $trait_name Impl>] {}

Expand Down Expand Up @@ -270,6 +236,45 @@ macro_rules! __devirt_define {

(@spec_decl) => {};

// ── @blanket_bridge ────────────────────────────────────────────────────
//
// Generates method bodies for the blanket
// `impl<T: FooBase> __FooImpl for T { ... }`.
// Each `__spec_*` method delegates to the corresponding
// `FooBase::method` call.

// &self
(@blanket_bridge $trait_name:ident,
$(#[$_attr:meta])*
fn $method:ident(&self $(, $arg:ident : $argty:ty)*) $(-> $ret:ty)?;
$($rest:tt)*
) => {
$crate::__paste! {
#[inline(always)]
fn [<__spec_ $method>](&self $(, $arg: $argty)*) $(-> $ret)? {
[<$trait_name Base>]::$method(self $(, $arg)*)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the declared base trait name in bridge methods

__devirt_define!(@trait ...) now takes an explicit $base_name, but the generated blanket bridge still hardcodes [<$trait_name Base>] when forwarding each __spec_* method. If a caller provides any base name other than the exact {Trait}Base spelling, expansion produces references to a non-existent trait and fails to compile. This is a functional regression in the new macro signature and should use $base_name consistently in bridge generation.

Useful? React with 👍 / 👎.

}
}
$crate::__devirt_define!{@blanket_bridge $trait_name, $($rest)*}
};

// &mut self
(@blanket_bridge $trait_name:ident,
$(#[$_attr:meta])*
fn $method:ident(&mut self $(, $arg:ident : $argty:ty)*) $(-> $ret:ty)?;
$($rest:tt)*
) => {
$crate::__paste! {
#[inline(always)]
fn [<__spec_ $method>](&mut self $(, $arg: $argty)*) $(-> $ret)? {
[<$trait_name Base>]::$method(self $(, $arg)*)
}
}
$crate::__devirt_define!{@blanket_bridge $trait_name, $($rest)*}
};
Comment on lines +234 to +261

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The @blanket_bridge macro arms in the declarative macro expansion only match method signatures ending in a semicolon (e.g., fn method(&self);). This means that traits defined using the declarative devirt! macro with default method bodies will fail to generate the necessary bridge implementations for cold types. While the declarative macro is documented as having limited syntax support, this is a notable limitation for the new Base trait functionality.


(@blanket_bridge $trait_name:ident,) => {};

// ── @inherent_decl ──────────────────────────────────────────────────────
//
// Emits inherent methods on `impl dyn $trait_name { ... }` that do
Expand Down Expand Up @@ -578,11 +583,13 @@ macro_rules! devirt {
$($methods:tt)*
}
) => {
$crate::__devirt_define! {
@trait []
$(#[$meta])*
$vis $name [$($hot),+] {
$($methods)*
$crate::__paste! {
$crate::__devirt_define! {
@trait []
$(#[$meta])*
$vis $name [<$name Base>] [$($hot),+] {
$($methods)*
}
}
}
};
Expand All @@ -594,11 +601,13 @@ macro_rules! devirt {
$($methods:tt)*
}
) => {
$crate::__devirt_define! {
@trait [unsafe]
$(#[$meta])*
$vis $name [$($hot),+] {
$($methods)*
$crate::__paste! {
$crate::__devirt_define! {
@trait [unsafe]
$(#[$meta])*
$vis $name [<$name Base>] [$($hot),+] {
$($methods)*
}
}
}
};
Expand Down Expand Up @@ -659,7 +668,7 @@ mod primitives {

crate::__devirt_define! {
@trait []
pub Probe [Hot, Also] {
pub Probe ProbeBase [Hot, Also] {
fn get(&self) -> u64;
fn set(&mut self, v: u64);
}
Expand Down Expand Up @@ -778,4 +787,34 @@ mod primitives {
let boxed: Box<dyn Probe> = Box::new(Hot { val: 5 });
assert_eq!(boxed.get(), 5);
}

/// Cold types can implement `ProbeBase` directly (no devirt macro)
/// and still participate in `dyn Probe` dispatch via the blanket.
struct NakedCold {
val: u64,
}

impl ProbeBase for NakedCold {
fn get(&self) -> u64 { self.val.wrapping_mul(2) }
fn set(&mut self, v: u64) { self.val = v.wrapping_mul(2); }
}

#[test]
fn cold_type_via_base_trait_ref() {
let nc = NakedCold { val: 5 };
assert_eq!((&nc as &dyn Probe).get(), 10);
}

#[test]
fn cold_type_via_base_trait_mut() {
let mut nc = NakedCold { val: 0 };
(&mut nc as &mut dyn Probe).set(3);
assert_eq!(nc.val, 6);
}

#[test]
fn cold_type_via_base_trait_box() {
let boxed: Box<dyn Probe> = Box::new(NakedCold { val: 7 });
assert_eq!(boxed.get(), 14);
}
}
8 changes: 4 additions & 4 deletions crates/core/tests/kani.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ mod n1 {

devirt::__devirt_define! {
@trait []
pub Trait1 [Hot] {
pub Trait1 Trait1Base [Hot] {
fn compute(&self, x: u64) -> u64;
fn notify(&self, x: u64);
fn transform(&mut self, x: u64) -> u64;
Expand Down Expand Up @@ -122,7 +122,7 @@ mod n2 {

devirt::__devirt_define! {
@trait []
pub Trait2 [HotA, HotB] {
pub Trait2 Trait2Base [HotA, HotB] {
fn compute(&self, x: u64) -> u64;
fn notify(&self, x: u64);
fn transform(&mut self, x: u64) -> u64;
Expand Down Expand Up @@ -257,7 +257,7 @@ mod n3 {

devirt::__devirt_define! {
@trait []
pub Trait3 [HotA, HotB, HotC] {
pub Trait3 Trait3Base [HotA, HotB, HotC] {
fn compute(&self, x: u64) -> u64;
fn notify(&self, x: u64);
fn transform(&mut self, x: u64) -> u64;
Expand Down Expand Up @@ -405,7 +405,7 @@ mod vt {

devirt::__devirt_define! {
@trait []
pub TraitVt [Hot] {
pub TraitVt TraitVtBase [Hot] {
fn compute(&self, x: u64) -> u64;
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/all_arms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct ColdType {

devirt::__devirt_define! {
@trait []
pub AllArms [Hot] {
pub AllArms AllArmsBase [Hot] {
fn ref_nonvoid(&self, x: f64) -> f64;
fn ref_void(&self, x: f64);
fn mut_nonvoid(&mut self, x: f64) -> f64;
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/method_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ struct Hot {

devirt::__devirt_define! {
@trait []
pub Checked [Hot] {
pub Checked CheckedBase [Hot] {
/// Computes the value.
#[must_use]
fn compute(&self) -> f64;
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/missing_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ struct Foo;

devirt::__devirt_define! {
@trait []
pub TwoMethods [Foo] {
pub TwoMethods TwoMethodsBase [Foo] {
fn first(&self) -> i32;
fn second(&self) -> i32;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/missing_method.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ error[E0046]: not all trait items implemented, missing: `__spec_second`
|
3 | / devirt::__devirt_define! {
4 | | @trait []
5 | | pub TwoMethods [Foo] {
5 | | pub TwoMethods TwoMethodsBase [Foo] {
6 | | fn first(&self) -> i32;
... |
9 | | }
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/multi_arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct Widget {

devirt::__devirt_define! {
@trait []
pub MultiArg [Widget] {
pub MultiArg MultiArgBase [Widget] {
fn add(&self, a: f64, b: f64) -> f64;
fn set(&mut self, a: f64, b: f64);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/multi_hot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ struct C;

devirt::__devirt_define! {
@trait []
pub MultiHot [A, B, C] {
pub MultiHot MultiHotBase [A, B, C] {
fn id(&self) -> u8;
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/pub_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct Inner {
devirt::__devirt_define! {
@trait []
/// A public trait with documentation.
pub DocTrait [Inner] {
pub DocTrait DocTraitBase [Inner] {
/// Returns the inner value.
fn get(&self) -> i32;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/single_hot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ struct Foo {

devirt::__devirt_define! {
@trait []
pub SingleHot [Foo] {
pub SingleHot SingleHotBase [Foo] {
fn get(&self) -> f64;
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/unsafe_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct Cold {

devirt::__devirt_define! {
@trait [unsafe]
pub Trusted [Hot] {
pub Trusted TrustedBase [Hot] {
fn verify(&self) -> bool;
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/ui/wrong_signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ struct Bar;

devirt::__devirt_define! {
@trait []
pub WrongSig [Bar] {
pub WrongSig WrongSigBase [Bar] {
fn compute(&self, x: f64) -> f64;
}
}
Expand Down
Loading
Loading