Rust 中的異構集合
在某些情況下,當編寫軟件時,開發人員會遇到異構集合的需求——也就是說,可以存儲不同類型對象的集合。在 Rust 中,開發人員可以通過不同的方式實現這一目標,並進行不同的權衡。
使用枚舉
Rust 枚舉是實現這一目標的好方法,如果要存儲的對象的所有實現在開發時都是已知的,那麼開發人員可以創建一個枚舉來封裝每種可能的類型,然後爲這些枚舉創建一個集合。
然後,爲了訪問內部類的方法和屬性,可以使用匹配表達式來檢索內部對象。
enum ComponentType {
FirstComponent(MyFirstComponent),
SecondComponent(MySecondComponent),
}
struct MyFirstComponent {
}
impl MyFirstComponent {
fn do_first_component_thing(&self) {
println!("First Component");
}
}
struct MySecondComponent {
}
impl MySecondComponent {
fn do_second_component_thing(&self) {
println!("Second Component");
}
}
fn main() {
// 創建一個枚舉集合
let mut components: Vec<ComponentType> = Vec::new();
// 將枚舉添加到集合中,封裝目標類型
components.push(ComponentType::FirstComponent(MyFirstComponent {}));
components.push(ComponentType::SecondComponent(MySecondComponent {}));
// 使用匹配表達式從枚舉中檢索對象,並訪問方法和屬性
if let ComponentType::FirstComponent(component) = &components[0] {
component.do_first_component_thing();
}
}
這種方法的一個缺點是,在編寫代碼時需要確切知道這些被存儲的組件類型。
使用 Trait
另一種方法是使用特徵作爲替代解決方案,其中:
-
組件的通用方法定義在 Component trait 中。
-
每個相關組件結構體實現了組件特徵。
-
由於添加到集合中的對象的大小是未知的,因此需要將對象包裝在 Box 中。
trait Component {
fn do_component_thing(&self);
}
struct MyFirstComponent {}
impl Component for MyFirstComponent {
fn do_component_thing(&self) {
println!("First Component");
}
}
struct MySecondComponent {}
impl Component for MySecondComponent {
fn do_component_thing(&self) {
println!("Second Component");
}
}
fn main() {
let mut components: Vec<Box<dyn Component>> = Vec::new();
components.push(Box::new(MyFirstComponent { }));
components.push(Box::new(MySecondComponent { }));
components[0].do_component_thing();
components[1].do_component_thing();
}
當只需要訪問所有特徵中的公共方法時,這種方法工作得很好。這種方法的一個缺點是元素總是在堆上分配,另一個缺點是隻能訪問公共方法。
使用 Any Trait
Rust 文檔將 Any 類型描述爲一個模擬動態類型的 Trait,它提供了一個向下轉換方法,允許將一個類型轉換爲不同的類型。
use std::any::Any;
struct MyFirstComponent {
}
impl MyFirstComponent {
fn do_first_component_thing(&self) {
println!("First Component");
}
}
struct MySecondComponent {
}
impl MySecondComponent {
fn do_second_component_thing(&self) {
println!("Second Component");
}
}
fn main() {
let mut components: Vec<Box<dyn Any>> = Vec::new();
components.push(Box::new(MyFirstComponent {}));
components.push(Box::new(MySecondComponent {}));
if let Some(component) =
components[0].downcast_ref::<MyFirstComponent>() {
component.do_first_component_thing();
}
if let Some(component) =
components[1].downcast_ref::<MySecondComponent>() {
component.do_second_component_thing();
}
}
雖然這仍然會始終在堆上分配對象,但現在可以在數據結構中使用不同的組件類型,將它們轉換爲原始類型並訪問組件特定的屬性和方法。
不過,有一個小問題——沒有限制哪些類型可以添加到結構中。
混合 Any 和 Trait
Any 可以與 Trait 一起使用來爲對象創建邊界。訣竅是在 trait 中添加一個方法,將對象轉換爲 Any,然後將其向下轉換爲其他對象。然後,每個結構體都必須實現 trait 和轉換方法:
use std::any::Any;
trait Component {
fn as_any(&self) -> &dyn Any;
}
struct MyFirstComponent {
}
impl MyFirstComponent {
fn do_first_component_thing(&self) {
println!("First Component");
}
}
impl Component for MyFirstComponent {
fn as_any(&self) -> &dyn Any {
self
}
}
struct MySecondComponent {
}
impl MySecondComponent {
fn do_second_component_thing(&self) {
println!("Second Component");
}
}
impl Component for MySecondComponent {
fn as_any(&self) -> &dyn Any {
self
}
}
fn main() {
let mut components: Vec<Box<dyn Component>> = Vec::new();
components.push(Box::new(MyFirstComponent {}));
components.push(Box::new(MySecondComponent {}));
if let Some(component) =
components[0].as_any().downcast_ref::<MyFirstComponent>() {
component.do_first_component_thing();
}
if let Some(component) =
components[1].as_any().downcast_ref::<MySecondComponent>() {
component.do_second_component_thing();
}
}
雖然這仍然會在堆上分配對象,但對象特定的方法和屬性可以與向下轉換一起使用,並且集合綁定到實現該特徵的對象。一個很大的缺點是必須爲每個對象實現 trait,這恰恰是樣板代碼。
使用過程宏來減少樣板代碼
使用過程性宏來實現減少樣板代碼的解決方案:
// 派生宏需要駐留在自己的crate中
#[proc_macro_derive(Component)]
pub fn component_macro_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl Component for #name {
fn as_any(&self) -> &dyn Any {
self
}
}
};
gen.into()
}
// Component仍然存在於項目文件中.
#[derive(Component)]
struct MyFirstComponent {
}
impl MyFirstComponent {
fn do_first_component_thing(&self) {
println!("First Component");
}
}
總結
在 Rust 中有不同的方法來實現異構集合,雖然使用枚舉似乎被認爲是最常用的方法,但並不適合所有情況。在某些特定情況下,可以使用 Trait 和 Any。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/MLZaC8nq6KoHenK73_W6JA