Your argument is very true, but in the larger picture it comes with an unpleasant aftertaste. A closer look at `dyn`

shows a mechanism for type erasure or so-called upcast. In essence it smashes the type system. We can go further and generate all necessary dispatch tables by hand, which allows for separation of dispatch table and object, and thus for general polymorphic ABIs.

For example, consider the simple generic power function

```
trait Monoid {
fn one() -> Self;
fn mul(&self, x: &Self) -> Self;
}
fn pow<T: Monoid>(x: T, n: u32) -> T {
let mut y = T::one();
for _ in 0..n {y = y.mul(&x);}
return y;
}
impl Monoid for i32 {
fn one() -> Self {1}
fn mul(&self, x: &Self) -> Self {self*x}
}
```

Now we would like `pow`

to be runtime polymorphic instead. At first the structure of the dispatch table:

```
struct MonoidPtr(Box<dyn std::any::Any>);
struct MonoidType {
one: &'static dyn Fn() -> MonoidPtr,
mul: &'static dyn Fn(&MonoidPtr,&MonoidPtr) -> MonoidPtr
}
```

Implement it for `Monoid`

:

```
fn monoid_type<T: Monoid + 'static>() -> MonoidType {
let one = &|| -> MonoidPtr {MonoidPtr(Box::new(T::one()))};
let mul = &|a: &MonoidPtr, b: &MonoidPtr| -> MonoidPtr {
if let Some(a) = a.0.downcast_ref::<T>() {
if let Some(b) = b.0.downcast_ref::<T>() {
return MonoidPtr(Box::new(a.mul(b)));
}
}
unreachable!();
};
return MonoidType{one,mul};
}
```

The actual polymorphic code (which is monomorphic from the compiler's point of view):

```
fn pow_poly(t: &MonoidType, x: &MonoidPtr, n: u32) -> MonoidPtr {
let mut y = (t.one)();
for _ in 0..n {y = (t.mul)(&y,&x);}
return y;
}
```

Finally a type safe wrapper:

```
fn pow<T: Monoid + 'static>(x: T, n: u32) -> T {
let t = monoid_type::<T>();
let px = MonoidPtr(Box::new(x));
return match pow_poly(&t,&px,n).0.downcast::<T>() {
Ok(y) => *y,
_ => unreachable!()
};
}
```