Reference: Talk
In case of Generics: (Static Dispatch)
- Compiler create a new copy of the function for each type it is used with.
- Better Optimizations:
- Functions can be optimized differently for each type.
- Function calls can be limited
- More code to generate:
- Larger executables
- Longer compile times
In case of Trait Objects: (Dynamic Dispatch)
- Compiler creates one copy of the function, uses trait object's
vtable
- Adds some overhead, prevents type-specific optimzations
- Less code to generate:
- smaller executables, faster compile times.
- Static Dispatch: In static dispatch we substitute for functions while compile type.
trait Printable {
fn format(&self) -> String;
}
impl Printable for i32 {
fn format(&self) -> String {
format!("i32 {}", *self)
}
}
impl Printable for String {
fn format(&self) -> String {
format!("String {}", *self)
}
}
fn print_it<T: Printable>(z: T) {
println!("{}", z.format());
}
fn main() {
let a = 123;
let b = "Hello ".to_string();
print_it(a);
print_it(b);
}
- Dynamic Dispatch
struct Circle { radius: f64 }
struct Square { side: f64 }
trait Shape {
fn area(&self) -> f64;
}
impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
impl Shape for Circle {
fn area(&self) -> f64 {
self.radius * self.radius * std::f64::consts: PI
}
}
fn main() {
let shapes: [&Shape; 4] = [
&Circle { radius: 1.0 },
&Square { side: 3.0 },
&Circle { radius: 2.0 },
&Square { side: 3.0 }
];
for (i, shape) in shapes.iter().enumerate() {
println!("Shape #{} has area {}", i, shape.area());
}
}