Giao diện
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ểm | Nhược điểm |
|---|---|
| ✅ Zero runtime overhead | ❌ Code 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ểm | Nhượ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 copiesMarker 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 SyncSend 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 SendSync 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
| Type | Send | Sync | Lý do |
|---|---|---|---|
i32, String | ✅ | ✅ | Primitive/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 T | ❌ | ❌ | Raw 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
Copy và Drop 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:
- Không có generic type parameters trong methods
- Không trả về
Self - Không có
where Self: Sizedbounds
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