Why can't a dyn compatible trait use generics
⚓ Rust 📅 2025-09-22 👤 surdeus 👁️ 7I am working with dynamic dispaching in rust and I understand how it works (creating a vtable ect) the one thing I don't understand is why the functions inside these traits can't have function parameters.
My trait looks like this:
pub trait Number: Float + fmt::Debug + Any {}
impl<T: Float + fmt::Debug + Any> Number for T {}
pub trait Math<N: Number> {
fn dbg(&self, f: &mut fmt::Formatter<'_> -> fmt::Result;
fn cloned(&self) -> Box<dyn Math<N>>;
fn evaluate(&self, variables: &HashMap<&str, Box<dyn Math<N>>>) -> EvalResult<N>;
// function I would like to add but can't
fn map<F: Fn(N) -> O, O: Number>(&self, fn: F) -> Box<dyn Math<N>>;
}
What I don't understand is why the rules of dynamic traits can use functions with generic types.
With typical generic functions rust creates an implementation for each function, i.e.
fn main() {
foo(32);
foo(12.0);
foo(false);
}
fn foo<T: fmt::Debug>(val: T) {
println!("{val:?}")
}
generates a function for each used case
fn main() {
foo_i32(32);
foo_f64(12.0);
foo_bool(false);
}
fn foo_i32(val: i32) {
println!("{val:?}")
}
fn foo_f64(val: f64) {
println!("{val:?}")
}
fn foo_bool(val: bool) {
println!("{val:?}")
}
so why can a dyn compatible trait do the same
fn main() {
let a = FooA::<f64>::new(
FooB::<f64>::new(
FooA::<f64>::new(12.0),
FooA::f64::new(-1.0)
)
);
let b = a.cloned().map::<f32, _>(|x| x as i32);
let c = a.cloned().map::<bool, _>(|x| x > 0.0);
let d = b.cloned().map::<f32, _>(|x| x * x);
println!("a: {a:?}"); // FooA { val: FooB { a: FooA { val: 12.0 }, b: FooA { val: -1.0 } } }
println!("b: {b:?}"); // FooA { val: FooB { a: FooA { val: 12.0 }, b: FooA { val: -1.0 } } }
println!("c: {c:?}"); // FooA { val: FooB { a: FooA { val: true }, b: FooA { val: false } } }
println!("d: {d:?}"); // FooA { val: FooB { a: FooA { val: 144.0 }, b: FooA { val: 1.0 } } }
}
trait Foo<T> {
fn map<F: Fn(T) ->O, O>(&self, func: F)) -> Box<dyn Foo<O>>;
fn cloned(&self) -> Box<dyn Foo<T>> where T: Clone;
fn dbg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result where T: fmt::Debug;
}
#[derive(Debug, Clone)]
struct FooA<T> {
val: Box<dyn Foo<T>>
}
impl<T> FooA<T> {
fn new(val: impl Foo<T>) -> Self {
FooA { val: Box::new(val) }
}
}
impl<T> Foo<T> for FooA<T> {
fn map<O: From<T>>(&self) -> Box<dyn Foo<O>> {
Box::new(FooA::<O>::new(self.val.into()))
}
fn cloned(&self) -> Box<dyn Foo<T>>
where
T: Clone
{
Box::new(self.clone())
}
fn dbg(&self, f: &mut Formatter<'_>) -> fmt::Result
where
T: fmt::Debug
{
<Self as fmt::Debug>::fmt(self, f)
}
}
#[derive(Debug)]
struct FooB<T> {
a: Box<dyn Foo<T>>,
b: Box<dyn Foo<T>>,
}
impl<T: Clone> Clone for FooB<T> {
fn clone(&self) -> Self {
FooB {
a: self.a.cloned(),
b: self.b.cloned()
}
}
}
impl<T> FooB<T> {
fn new(a: impl Foo<T>, b: impl Foo<T>) -> Self {
FooB {
a: Box::new(a),
b: Box::new(b)
}
}
}
impl<T> Foo<T> for FooB<T> {
fn map<F: Fn(&T) -> O, O>(&self, func: F) -> Box<dyn Foo<O>> {
Box::new( FooB {
a: self.a.map(func),
b: self.b.map(func)
})
}
fn cloned(&self) -> Box<dyn Foo<T>>
where
T: Clone
{
Box::new(self.clone())
}
fn dbg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
where
T: fmt::Debug
{
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<T> Foo<T> for T {
fn map::<F: Fn(&T) -> O, O>(&self, func: F) -> Box<dyn Foo<O>> {
Box::new(func(self))
}
fn cloned(&self) -> Box<dyn Foo<T>>
where
T: Clone
{
Box::new(self.clone())
}
fn dbg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<T: fmt::Debug> fmt::Debug for dyn Foo<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.dbg(f)
}
}
which would generate the following vtables:
// Foo_f64
fn foo_f64_map_f32_{closure_id}(&self, func: {closure_id}) -> Box<dyn Foo_f32> { .. }
fn foo_f64_map_bool_{closure_id}(&self, func: {closure_id}) -> Box<dyn Foo_bool> { .. }
fn foo_f64_cloned(&self) -> Box<dyn Foo_f64> { .. }
fn foo_f64_dbg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
// Foo_f32
fn foo_f32_map_f32_{closure_id}(&self, func: {closure_id}) -> Box<dyn Foo_f32> { .. }
fn foo_f32_cloned(&self) -> Box<dyn Foo_f32> { .. }
fn foo_f32_dbg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
// Foo_bool
fn foo_bool_dbg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
which would generate this vtable for both FooA and FooB
it seems that in any case only a finite number of functions need to be created in the vtable for each instantiated implementer of Foo
2 posts - 2 participants
🏷️ Rust_feed