Hiểu tường tận Trait Dispatch trong Rust: Static vs. Dynamic

Khi viết code Rust, bạn sẽ thường xuyên bắt gặp impl Trait&dyn Trai nhìn thì tưởng na ná nhau, nhưng thực chất lại đại diện cho hai cơ chế Trait Dispatch hoàn toàn khác nhau: static dispatch (tĩnh) và dynamic dispatch (động).

Việc hiểu sâu hai cơ chế này là chìa khóa để viết code Rust hiệu quả, tận dụng tối đa khả năng tối ưu mà ngôn ngữ mang lại.

1.Trait Dispatch là gì?

Trait dispatch là cách mà Rust xác định và gọi phương thức từ trait. Tùy vào cách sử dụng, Rust sẽ quyết định thực hiện điều đó tại thời điểm biên dịch (compile time) hoặc thời điểm chạy (runtime).

1.1 Static Dispatch - Phân phối tĩnh

Static dispatch xảy ra khi sử dụng impl Trait hoặc generic parameter có ràng buộc trait:

fn do_something<T: MyTrait>(x: T) {
    x.method();
}

Cơ chế là Rust sẽ thực hiện monomorphization (quá trình nhân bản) hàm generic cho từng kiểu cụ thể mà bạn sử dụng. Mỗi bản sao sẽ có tất cả kiểu dữ liệu cụ thể thay cho generic. Điều này giúp compiler biết chính xác địa chỉ của method cần gọi và có thể tối ưu rất mạnh

Ưu điểm:

Nhược điểm

1.2 Dynamic Dispatch - Phân phối động

Dynamic dispatch xảy ra khi sử dụng &dyn Trait hoặc Box:

fn do_something(x: &dyn MyTrait) {
    x.method();
}

Rust sẽ tạo một trait object, bao gồm:

Ưu điểm:

Nhược điểm


Object Safety - Tính an toàn của trait object

Không phải trait nào cũng có thể dùng dưới dạng trait object. Một trait chỉ được gọi là object-safe nếu thỏa các điều kiện:

Ví dụ, trait sau không object-safe:

trait Clone {
    fn clone(&self) -> Self;
}

Bạn có thể dùng Where Self: Sized để giới hạn phương thức không được gọi qua trait object.