Skip to content

Trait System Deep Tech

Static vs Dynamic Dispatch: Hiểu cách Rust xử lý Polymorphism

Trait Basics

Trait định nghĩa shared behavior giữa các types:

rust
trait Drawable {
    fn draw(&self);
    
    // Default implementation
    fn description(&self) -> String {
        String::from("A drawable object")
    }
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing circle with radius {}", self.radius);
    }
}

impl Drawable for Rectangle {
    fn draw(&self) {
        println!("Drawing {}x{} rectangle", self.width, self.height);
    }
}

Static Dispatch (Monomorphization)

Cơ chế hoạt động

Khi sử dụng generics T: Trait, Rust compiler tạo bản copy riêng của function cho mỗi concrete type:

rust
// Generic function
fn draw_twice<T: Drawable>(item: &T) {
    item.draw();
    item.draw();
}

fn main() {
    let circle = Circle { radius: 5.0 };
    let rect = Rectangle { width: 10.0, height: 5.0 };
    
    draw_twice(&circle);  // Gọi draw_twice_Circle
    draw_twice(&rect);    // Gọi draw_twice_Rectangle
}

Sau Monomorphization, compiler tạo:

rust
// Compiler tự động generate:
fn draw_twice_Circle(item: &Circle) {
    item.draw();  // Gọi Circle::draw trực tiếp
    item.draw();
}

fn draw_twice_Rectangle(item: &Rectangle) {
    item.draw();  // Gọi Rectangle::draw trực tiếp
    item.draw();
}

Assembly Output (Simplified)

asm
; draw_twice::<Circle>
draw_twice_Circle:
    call Circle::draw    ; Direct call, no indirection
    call Circle::draw
    ret

; draw_twice::<Rectangle>
draw_twice_Rectangle:
    call Rectangle::draw ; Direct call
    call Rectangle::draw
    ret

💡 ZERO-COST

Static dispatch có zero runtime overhead — compiler biết chính xác function nào được gọi tại compile-time. Function call được inlined nếu có lợi.

Trade-offs

Ưu điểmNhược điểm
✅ Zero runtime overheadCode bloat - mỗi type = thêm code
✅ Có thể inline❌ Compile time tăng
✅ CPU branch prediction tốt❌ Không thể mix types trong collection

Dynamic Dispatch (Trait Objects)

Cơ chế hoạt động

dyn Trait sử dụng vtable để lookup method tại runtime:

rust
fn draw_all(items: &[&dyn Drawable]) {
    for item in items {
        item.draw();  // Vtable lookup tại runtime
    }
}

fn main() {
    let circle = Circle { radius: 5.0 };
    let rect = Rectangle { width: 10.0, height: 5.0 };
    
    // Có thể mix different types!
    let shapes: Vec<&dyn Drawable> = vec![&circle, &rect];
    draw_all(&shapes);
}

Vtable Layout Chi tiết

&dyn Drawable (Fat Pointer - 16 bytes):
┌──────────────────────┐
│ data_ptr ────────────────▶ Concrete instance (Circle/Rectangle)
├──────────────────────┤
│ vtable_ptr ──────────────▶ ┌─────────────────────────────────┐
└──────────────────────┘     │ drop_in_place: fn(*mut ())     │
                             ├─────────────────────────────────┤
                             │ size: usize                     │
                             ├─────────────────────────────────┤
                             │ align: usize                    │
                             ├─────────────────────────────────┤
                             │ draw: fn(*const ()) ────────────▶ Circle::draw
                             ├─────────────────────────────────┤     hoặc
                             │ description: fn(*const ()) ──▶ Drawable::description
                             └─────────────────────────────────┘

Assembly Output (Dynamic Dispatch)

asm
; draw_all - Dynamic dispatch
draw_all:
    mov rax, [rdi+8]      ; Load vtable_ptr
    mov rcx, [rdi]        ; Load data_ptr
    call [rax+24]         ; Call vtable[draw_offset] - INDIRECT CALL
    ret

⚠️ OVERHEAD

Dynamic dispatch có small runtime cost:

  • Extra pointer indirection để access vtable
  • Indirect call (khó branch predict hơn)
  • Không thể inline

Trade-offs

Ưu điểmNhược điểm
✅ Nhỏ binary size (1 copy code)❌ Vtable lookup overhead
✅ Mix types trong collection❌ Không thể inline
✅ Compile time nhanh hơn❌ Indirect call (branch mispredict)

Performance Analysis

Benchmark Comparison

rust
use std::time::Instant;

// Static dispatch
fn sum_static<I: Iterator<Item = i32>>(iter: I) -> i32 {
    iter.sum()
}

// Dynamic dispatch  
fn sum_dynamic(iter: &mut dyn Iterator<Item = i32>) -> i32 {
    iter.sum()
}

fn benchmark() {
    let data: Vec<i32> = (0..10_000_000).collect();
    
    // Static: ~15ms
    let start = Instant::now();
    let _ = sum_static(data.iter().copied());
    println!("Static: {:?}", start.elapsed());
    
    // Dynamic: ~45ms (3x slower)
    let start = Instant::now();
    let _ = sum_dynamic(&mut data.iter().copied());
    println!("Dynamic: {:?}", start.elapsed());
}

Kết quả: Dynamic dispatch chậm hơn 2-5x trong tight loops do:

  • Vtable lookup mỗi lần gọi method
  • Không inline được
  • CPU branch prediction kém hiệu quả

Khi nào dùng?

rust
// ✅ Static dispatch: Hot path, performance-critical
fn process_items<T: Processor>(items: &[T]) { ... }

// ✅ Dynamic dispatch: Plugin systems, heterogeneous collections
fn plugins() -> Vec<Box<dyn Plugin>> { ... }

// ✅ Dynamic dispatch: Binary size optimization
fn draw_shape(shape: &dyn Shape) { ... }  // 1 copy
// vs
fn draw_shape<T: Shape>(shape: &T) { ... } // N copies

Marker Traits

Auto Traits

Marker traits không có methods, chỉ đánh dấu capabilities:

rust
// Compiler tự động implement nếu tất cả fields đều Send/Sync
struct MyStruct { data: i32 }  // auto Send + Sync

// Raw pointer phá vỡ auto-implementation
struct Wrapper { ptr: *const i32 }  // NOT Send, NOT Sync

Send Trait

Type có thể transfer ownership sang thread khác:

rust
use std::thread;

// ✅ Send: có thể chuyển sang thread khác
fn spawn_with<T: Send + 'static>(data: T) {
    thread::spawn(move || {
        // data được MOVE vào thread mới
        println!("{:?}", std::any::type_name::<T>());
    });
}

// ❌ NOT Send: Rc<T> - reference count không thread-safe
// use std::rc::Rc;
// spawn_with(Rc::new(42));  // ERROR: Rc<i32> is not Send

Sync Trait

Type có thể shared via reference giữa nhiều threads:

rust
use std::sync::Arc;
use std::thread;

// T: Sync nghĩa là &T: Send
fn share_across_threads<T: Sync + Send + 'static>(data: Arc<T>) {
    let handles: Vec<_> = (0..4).map(|_| {
        let data = Arc::clone(&data);
        thread::spawn(move || {
            // Nhiều threads cùng đọc &T
            println!("Thread sees data");
        })
    }).collect();
    
    for h in handles { h.join().unwrap(); }
}

Thread Safety Matrix

TypeSendSyncLý do
i32, StringPrimitive/owned data
&T (where T: Sync)Immutable reference
Rc<T>Non-atomic refcount
Arc<T>Atomic refcount
RefCell<T>Runtime borrow, not thread-safe
Mutex<T>Synchronized access
*const T, *mut TRaw pointers unsafe

Copy Trait

Type được bitwise copy thay vì move:

rust
// Điều kiện: tất cả fields phải Copy, không có Drop
#[derive(Copy, Clone)]
struct Point { x: f64, y: f64 }

let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1;  // COPY, không phải move
println!("{:?}", p1);  // p1 vẫn valid!

// ❌ Không thể Copy nếu có Drop
// #[derive(Copy)]  // ERROR
// struct MyVec { data: Vec<i32> }

Drop Trait

Custom destructor logic:

rust
struct Connection {
    id: u32,
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("Closing connection {}", self.id);
        // Cleanup logic here
    }
}

fn main() {
    let conn = Connection { id: 1 };
    // ...
}  // "Closing connection 1" in ra ở đây

⚠️ QUAN TRỌNG

CopyDrop không thể cùng implement cho một type. Copy nghĩa là bitwise copy, Drop nghĩa là cần cleanup — hai khái niệm mâu thuẫn.


Object Safety

Trait chỉ có thể thành dyn Trait nếu object-safe:

rust
// ✅ Object-safe
trait Drawable {
    fn draw(&self);
}

// ❌ NOT object-safe - có generic method
trait Converter {
    fn convert<T>(&self) -> T;  // ERROR as dyn
}

// ❌ NOT object-safe - trả về Self
trait Clonable {
    fn clone(&self) -> Self;  // ERROR as dyn
}

// ✅ Workaround: Box<Self>
trait ClonableObject {
    fn clone_box(&self) -> Box<dyn ClonableObject>;
}

Object Safety Rules:

  1. Không có generic type parameters trong methods
  2. Không trả về Self
  3. Không có where Self: Sized bounds

Bảng Tóm tắt

rust
// === STATIC DISPATCH ===
fn process<T: Trait>(item: T) { ... }  // Monomorphized

// === DYNAMIC DISPATCH ===
fn process(item: &dyn Trait) { ... }   // Vtable lookup

// === MARKER TRAITS ===
// Send: có thể chuyển ownership sang thread khác
// Sync: có thể share reference giữa threads (&T is Send)
// Copy: bitwise copy thay vì move
// Drop: custom destructor

// === OBJECT SAFETY ===
// - Không generic methods
// - Không return Self
// - Có thể dùng Box<Self> làm workaround