概要
Rust の std::any
モジュールには Any
という名前のトレイトがあります(ここで boost::any
を使ったことのある C++er は何かを察する)。Any
トレイトはほとんどあらゆる型に実装されるため、すなわちほとんどあらゆる型の参照を &Any
で受けることができます。
Any
は “A type to emulate dynamic typing” (動的型付けをエミュレートする型)と公式リファレンスで説明されています。これを用いることで**「あらゆる型の参照が入る変数を定義する」「実行時の型によって処理を分岐させる」**などといった動的型付けっぽいプログラミングが可能となります。しかもダウンキャスト(Any
から指定の型への変換1)の結果は Option
型で返ってくるため、パターンマッチを使うことで分岐処理を型安全に、かつ少ない記述量で書くことができます。
あらかじめ入る型の候補が分かっている場合には値持ちの enum
を使う方が筋がよさそうではあるけれど2、&Any
も一つの手段として覚えておくといざという時に役に立ちそうですね。
解説
(以下のコード例の動作は Wandbox の Rust HEAD 1.15.0-dev (5f128ed10 2016-12-06) で確認しています。実際のコードはこちら)
&Any
型のミュータブルな変数 a
に色々な型の参照を突っ込む例です。is::<T>
メソッドで &Any
が指定の型 T
の値を参照しているかどうかを判定できます。
use std::any::Any;
#[derive(Debug)] enum E { H, He, Li, Be }
struct S { x: u8, y: u8, z: u16 }
fn main() {
let v1 = 0xc0ffee_u32;
let v2 = E::He;
let v3 = S { x: 0xde, y: 0xad, z: 0xbeef };
let v4 = "hoge";
let mut a: &Any;
a = &v1;
assert!(a.is::<u32>());
a = &v2;
assert!(a.is::<E>());
a = &v3;
assert!(a.is::<S>());
a = &v4;
assert!(a.is::<&str>());
}
downcast_ref::<T>
メソッドを使うと指定の型へのダウンキャストができます。結果は Option<&T>
型で返ってくるため、if let
を使うと型による処理の分岐をいー感じに書けます。
fn print_any(a: &Any) {
if let Some(v) = a.downcast_ref::<u32>() {
println!("u32 {:x}", v);
} else if let Some(v) = a.downcast_ref::<E>() {
println!("enum E {:?}", v);
} else if let Some(v) = a.downcast_ref::<S>() {
println!("struct S {:x} {:x} {:x}", v.x, v.y, v.z);
} else {
println!("else!");
}
}
fn main() {
print_any(& 0xc0ffee_u32); // => u32 c0ffee
print_any(& E::He); // => enum E He
print_any(& S { x: 0xde, y: 0xad, z: 0xbeef }); // => struct S de ad beef
print_any(& "hoge"); // => else!
}
制限
非 'static
な参照を含む型には使えない
非 'static
な参照を含む型は Any
トレイトを実装しないため、その参照を &Any
で受けることはできません(コンパイルエラーになります)。
struct T<'a> { x: &'a i32 }
fn main() {
let answer = 42;
let v = T { x: &answer };
let mut a: &Any;
// a = &v; // Compile Error!
// (note: borrowed value must be valid for the static lifetime...)
}
以下のように 'static
な参照であればコンパイルが通ります。
static ANSWER: i32 = 42;
fn main() {
let v = T { x: &ANSWER };
let mut a: &Any;
a = &v;
assert!(a.is::<T>());
}
トレイトへのダウンキャストはできない
ダウンキャストの変換先は具象型に限定され、トレイトへの変換はできません。つまり a.downcast_ref::<Display>()
とか a.downcast_ref::<Clone>()
とか a.downcast_ref::<Eq>()
とかはムリ。
その他の機能
downcast_mut
メソッド
ミュータブルな参照にダウンキャストする downcast_mut
メソッドも用意されています。
let mut answer = 0;
{
let a: &mut Any;
a = &mut answer;
if let Some(x) = a.downcast_mut::<i32>() {
*x = 42;
}
}
assert_eq!(answer, 42);
Box<Any>
型
&Any
の代わりに Box<Any>
を使うこともできます。こちらの場合中身の値はヒープ領域上に配置されます。
Box<Any>
には downcast<T>
メソッドが用意されています。戻り値は Result<Box<T>, Box<Any>>
です。
let mut a: Box<Any>;
a = Box::new(42);
if let Ok(answer) = a.downcast::<i32>() {
assert_eq!(*answer, 42);
}
TypeId::of::<T>
関数
std::any::TypeId::of::<T>
関数で各具象型について一意に定まる TypeId
を得ることができます。
use std::any::TypeId;
println!("{:?}", TypeId::of::<u8>()); // => TypeId { t: 7100172564015177708 }
println!("{:?}", TypeId::of::<&str>()); // => TypeId { t: 3020856465566936866 }
TypeId
は opaque(不透明)なオブジェクトであり、内部の具体的な値に依存したコードを書いてはいけません(デバッグプリントすると中身見えちゃうけど)。Eq
, Hash
トレイトを実装しているので TypeId
同士を比較したり TypeId
を HashMap
のキーとして使ったりすることなどは可能です。
get_type_id
メソッド (unstable)
Any
トレイトの get_type_id
メソッドを用いてそのオブジェクトの TypeId
を得ることができます。**ただし unstable です。**そのうち関連関数(静的メソッド)に置き換えられるらしい。
#![feature(get_type_id)]
fn main() {
use std::any::Any;
println!("{:?}", (&0u8 as &Any).get_type_id()); // => TypeId { t: 7100172564015177708 }
println!("{:?}", (&"" as &Any).get_type_id()); // => TypeId { t: 3020856465566936866 }
}
内部動作
'static
な型 T
は Any
トレイトを以下のように実装します。TypeId::of::<T>
関数は内部でコンパイラ組み込みの std::intrinsics::type_id::<T>
関数を呼びます。
impl<T: 'static + ?Sized> Any for T {
#[unstable(feature = "get_type_id",
reason = "this method will likely be replaced by an associated static",
issue = "27745")]
fn get_type_id(&self) -> TypeId { TypeId::of::<T>() }
}
downcast_ref
の実装は「self.is::<T>()
(すなわち self.get_type_id() == TypeId::of::<T>()
)が真なら &T
にキャストして Some
に包んで返す」というシンプルな実装です。
impl Any {
...
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
if self.is::<T>() {
unsafe { Some(&*(self as *const Any as *const T)) }
} else {
None
}
}
...
}
まとめ
Rust でいわゆる実行時型情報 (RTTI, RunTime Type Identification) を扱う方法でした。Rust は色々と厳格な言語であるという印象が強いけど、こういうゆるふわっぽいこともできるよ(しかも型安全に!)、ということで紹介しました。