Dynamic typing trong Rust với std::any::Any

<- Quay về trang chủ

Xét một tình huống thường gặp trong lập trình: Giả sử ta có một interface kiểu Shape, và 2 class là Rectangle, Circle cùng implement interface Shape, sau đó ta tạo một danh sách tên là shapes để chứa tất cả các đối tượng có implement Shape.

Trong Rust code sẽ nhìn như thế này:

pub trait Shape {}

pub struct Rectangle {}
impl Shape for Rectangle {}

pub struct Circle {}
impl Shape for Circle {}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Rectangle {}),
        Box::new(Circle {}),
    ];
}

Trong quá trình làm việc với mảng shapes, có lúc chúng ta muốn lấy một giá trị ra và cast nó về kiểu Rectangle hoặc Circle để sử dụng, thường thì chúng ta sẽ làm như này:

let rect: &Rectangle = shapes.get(0).unwrap().as_ref();

Xong rồi sẽ bị Rust chửi vào mặt:

error[E0308]: mismatched types
  --> src/main.rs:15:28
   |
15 |     let rect: &Rectangle = shapes.get(0).unwrap().as_ref();
   |               ----------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Rectangle`, found trait object `dyn Shape`
   |               |
   |               expected due to this
   |
   = note: expected reference `&Rectangle`
              found reference `&dyn Shape`

Lỗi vì shapes là một vector chứa các object kiểu dyn Shape, nên khi dùng hàm get() để lấy một phần tử ra, phần tử đó sẽ mang kiểu dyn Shape.

Để có thể chuyển một object kiểu dyn Shape thành Rectangle, chúng ta có thể implement trait std::any::Any cho kiểu Rectangle.

pub trait Shape {
    fn as_any(&self) -> &dyn std::any::Any;
}

pub struct Rectangle {}
impl Shape for Rectangle {
    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

Từ bây giờ, chúng ta có thể gọi hàm .as_any() để chuyển một Shape về kiểu Any, rồi dùng hàm downcast_ref của Any để cast nó về kiểu mong muốn:

let shape: &dyn Shape = shapes.get(0).unwrap().as_ref();
let rect: &Rectangle = shape.as_any().downcast_ref().unwrap();