はじめに
ソースコードはgistを参照してください。
やりたいこと
様々な型を受け入れる構造体をつくりたい
問題
様々な型に対応するにはジェネリクスを使うのがよいですが、以下のようなコードはコンパイル不可能です。
b_vec
の型は1つ目のアイテムを解釈した瞬間から、Vec<B<i32>>
となってしまい、B<String>
を格納することはできません。
struct B<T> {
value: T
}
fn main() {
let b_vec = vec![
B {
value: 10 as i32,
},
B {
value: String::from("hi"),
}
];
}
解決
std::any::Any
トレイトとBox
を組み合わせたメンバを構造体に定義することで解決できます。
std::any::Any
トレイトは非'static
参照を含む型には実装できませんが、逆に言うとほとんどの型で使用することができます。(実装されています)
そのため、std::any::Any
トレイトオブジェクトにより様々な型を指定することが可能となります。
以下のようにして、Box
の中にstd::any::Any
トレイトオブジェクトを入れた型のメンバを構造体A
に定義します。すると、あらゆる型をもったAでVector
を生成してもVec<Box<dyn Any>>
となり、多様な型を受け入れられるようになります。
また、std::any::Any
トレイトにはdowncast_ref
メソッドがあり、これで元の型を取り戻すことができます。それもOption
型で取り出すことができるので大変便利です。
use std::any::Any;
struct A {
value: Box<dyn Any>
}
fn main() {
let a_vec = vec![
A {
value: Box::new(10),
},
A {
value: Box::new(String::from("hi")),
},
];
for v in a_vec.iter() {
if let Some(a) = v.value.downcast_ref::<i32>() {
println!("i32 {}", a);
} else if let Some(a) = v.value.downcast_ref::<String>() {
println!("string {}", a)
}
}
}
まとめ
std::any::Any
トレイトとBox
を組み合わせることにより、まるで動的型付けのようなコードを書くことができるようになりました。
参考