// Example 124: Dynamic Dispatch — dyn Trait vs impl Trait
//
// impl Trait = static dispatch (monomorphization, zero-cost, no vtable)
// dyn Trait = dynamic dispatch (vtable pointer, runtime flexibility)
use std::f64::consts::PI;
trait Shape {
fn area(&self) -> f64;
fn name(&self) -> &str;
}
struct Circle { radius: f64 }
struct Rect { width: f64, height: f64 }
struct Triangle { base: f64, height: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { PI * self.radius * self.radius }
fn name(&self) -> &str { "circle" }
}
impl Shape for Rect {
fn area(&self) -> f64 { self.width * self.height }
fn name(&self) -> &str { "rectangle" }
}
impl Shape for Triangle {
fn area(&self) -> f64 { 0.5 * self.base * self.height }
fn name(&self) -> &str { "triangle" }
}
// Approach 1: Static dispatch with impl Trait (monomorphized)
fn print_area_static(s: &impl Shape) {
println!("{}: {:.2}", s.name(), s.area());
}
fn approach1() {
let c = Circle { radius: 5.0 };
let r = Rect { width: 3.0, height: 4.0 };
print_area_static(&c); // compiles to direct call
print_area_static(&r); // separate monomorphized copy
}
// Approach 2: Dynamic dispatch with dyn Trait (vtable)
fn total_area_dynamic(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
fn approach2() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 5.0 }),
Box::new(Rect { width: 3.0, height: 4.0 }),
Box::new(Triangle { base: 6.0, height: 3.0 }),
];
let total = total_area_dynamic(&shapes);
println!("Dynamic total area: {:.2}", total);
// Can also use &dyn Shape (borrowed trait objects)
for s in &shapes {
println!(" {}: {:.2}", s.name(), s.area());
}
}
// Approach 3: Enum dispatch (closed set, no vtable)
enum ShapeEnum {
Circle(f64),
Rect(f64, f64),
Triangle(f64, f64),
}
impl ShapeEnum {
fn area(&self) -> f64 {
match self {
ShapeEnum::Circle(r) => PI * r * r,
ShapeEnum::Rect(w, h) => w * h,
ShapeEnum::Triangle(b, h) => 0.5 * b * h,
}
}
}
fn approach3() {
let shapes = vec![
ShapeEnum::Circle(5.0),
ShapeEnum::Rect(3.0, 4.0),
ShapeEnum::Triangle(6.0, 3.0),
];
let total: f64 = shapes.iter().map(|s| s.area()).sum();
println!("Enum total area: {:.2}", total);
}
fn main() {
println!("=== Approach 1: Static (impl Trait) ===");
approach1();
println!("\n=== Approach 2: Dynamic (dyn Trait) ===");
approach2();
println!("\n=== Approach 3: Enum Dispatch ===");
approach3();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_static_dispatch() {
let c = Circle { radius: 1.0 };
assert!((c.area() - PI).abs() < 1e-10);
}
#[test]
fn test_dynamic_dispatch() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Rect { width: 2.0, height: 3.0 }),
];
assert!((total_area_dynamic(&shapes) - 6.0).abs() < f64::EPSILON);
}
#[test]
fn test_enum_dispatch() {
let s = ShapeEnum::Rect(2.0, 3.0);
assert!((s.area() - 6.0).abs() < f64::EPSILON);
}
#[test]
fn test_trait_object_ref() {
let c = Circle { radius: 1.0 };
let s: &dyn Shape = &c;
assert_eq!(s.name(), "circle");
}
#[test]
fn test_heterogeneous_collection() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Rect { width: 1.0, height: 1.0 }),
];
assert_eq!(shapes.len(), 2);
assert!(shapes[0].area() > shapes[1].area()); // π > 1
}
}
(* Example 124: Dynamic Dispatch — dyn Trait vs impl Trait *)
(* OCaml uses virtual dispatch for objects, first-class modules for
trait-like polymorphism. *)
(* Approach 1: First-class modules — like dyn Trait *)
module type Shape = sig
val area : unit -> float
val name : unit -> string
end
let total_area (shapes : (module Shape) list) =
List.fold_left (fun acc (module S : Shape) -> acc +. S.area ()) 0.0 shapes
let circle r : (module Shape) = (module struct
let area () = Float.pi *. r *. r
let name () = "circle"
end)
let rect w h : (module Shape) = (module struct
let area () = w *. h
let name () = "rectangle"
end)
let approach1 () =
let shapes = [circle 5.0; rect 3.0 4.0; circle 2.0] in
let total = total_area shapes in
Printf.printf "Total area: %.2f\n" total;
assert (total > 90.0)
(* Approach 2: Object-oriented style *)
class virtual shape_obj = object
method virtual area : float
method virtual name : string
end
class circle_obj r = object
inherit shape_obj
method area = Float.pi *. r *. r
method name = "circle"
end
class rect_obj w h = object
inherit shape_obj
method area = w *. h
method name = "rectangle"
end
let approach2 () =
let shapes = [new circle_obj 5.0; new rect_obj 3.0 4.0] in
let total = List.fold_left (fun acc s -> acc +. s#area) 0.0 shapes in
Printf.printf "OO total: %.2f\n" total
(* Approach 3: Variant-based dispatch — monomorphic *)
type shape_v = Circle of float | Rect of float * float
let area_v = function
| Circle r -> Float.pi *. r *. r
| Rect (w, h) -> w *. h
let approach3 () =
let shapes = [Circle 5.0; Rect (3.0, 4.0)] in
let total = List.fold_left (fun acc s -> acc +. area_v s) 0.0 shapes in
Printf.printf "Variant total: %.2f\n" total
let () =
approach1 ();
approach2 ();
approach3 ();
Printf.printf "✓ All tests passed\n"