Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Trait object và hướng đối tượng

Vấn đề: một collection chứa nhiều kiểu cụ thể khác nhau

Vec<T> chỉ chứa được một T. Generic (chương Generic, Trait, Lifetime) cũng chỉ chọn một kiểu cụ thể tại mỗi lần dùng — không giúp được khi cần một danh sách các thành phần giao diện, mỗi phần tử là một kiểu cụ thể khác nhau, nhưng tất cả cùng cần vẽ được:

pub trait Ve {
    fn ve(&self);
}

pub struct Nut {
    pub nhan: String,
}

impl Ve for Nut {
    fn ve(&self) {
        println!("Vẽ nút bấm: {}", self.nhan);
    }
}

pub struct O {
    pub da_chon: bool,
}

impl Ve for O {
    fn ve(&self) {
        println!("Vẽ ô chọn, đã chọn: {}", self.da_chon);
    }
}

fn main() {
    let cac_thanh_phan: Vec<Box<dyn Ve>> = vec![
        Box::new(Nut {
            nhan: String::from("Đồng ý"),
        }),
        Box::new(O { da_chon: true }),
    ];

    for tp in cac_thanh_phan.iter() {
        tp.ve();
    }
}

Box<dyn Ve> là một trait object: một con trỏ tới dữ liệu (Box) cộng một con trỏ tới bảng phương thức (vtable) của trait Ve ứng với đúng kiểu cụ thể đang nằm trong Box đó. Kiểu Vec<Box<dyn Ve>> chứa được Nut lẫn O cùng lúc — điều Vec<T> với T cố định không bao giờ làm được.

Static dispatch và dynamic dispatch

Generic (từ chương trước) dùng static dispatch: trình biên dịch biết chính xác kiểu cụ thể ngay lúc biên dịch (nhờ monomorphization), gọi thẳng đúng hàm — không có gì phải tra cứu lúc chạy, nhưng đổi lại một collection generic chỉ chứa được một kiểu cụ thể.

Trait object dùng dynamic dispatch: kiểu cụ thể đằng sau dyn Ve chỉ biết được lúc chạy, nên gọi tp.ve() phải tra vtable để tìm đúng hàm ve của kiểu cụ thể đang nằm ở đó — thêm một lượt tra cứu gián tiếp so với static dispatch, và trình biên dịch cũng mất khả năng inline lời gọi đó (vì không biết trước sẽ gọi hàm nào). Đổi lại, có được thứ static dispatch không thể: một collection chứa nhiều kiểu cụ thể khác nhau, miễn cùng implement một trait.

Đây là đánh đổi tường minh giữa hai công cụ giải quyết hai vấn đề khác nhau: cần hiệu năng tối đa và chỉ một kiểu cụ thể tại một chỗ — dùng generic; cần chứa nhiều kiểu cụ thể không biết trước hết ngay lúc viết code — dùng trait object.

Không phải trait nào cũng thành trait object được

Một trait chỉ an toàn cho object (object-safe) nếu mọi method của nó không trả về Self và không có tham số kiểu generic — cả hai điều đó đòi hỏi biết kiểu cụ thể ngay lúc biên dịch, mâu thuẫn với bản chất “kiểu cụ thể chỉ biết lúc chạy” của trait object. Cố tạo dyn từ một trait không thoả điều kiện này là lỗi biên dịch, kèm gợi ý rõ ràng trait đó “không thể trở thành object”.