Dynamic typing trong Rust với std::any::Any
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();