100日後にRustをちょっと知ってる人になる: [Day 41]幽霊型 (Phantom Type)
Day 41 のテーマ
Day 40 では、ジェネリクスで書くとやや複雑になりそうな場合に 関連型 という宣言の仕方を使って書き換えることを見てみました。
型パラメータを利用して構造体や関数などの型を汎化させる仕組みのジェネリクスですが、この型パラメータを用いたデザインパターンがあります。
それが、Phantom Type といういもので、日本語だと幽霊型と呼ぶそうです。
個人的に今まで使ってきた言語では幽霊型と呼ぶようなものがなかった(僕が知らなかっただけ?)なので見ていこうと思います。
幽霊型
まず幽霊型がどういうものなのか、というのを様々な言語で定義でけられているものを見てみました。
Haskel, Swift, TypeScript, Sacala, Elm など大体次のような内容で説明されていました。
型パラメータを使用してコンパイル時には影響を与えるが、実行時の挙動には影響を与えない型を用いたデザインパターンの一種
正直良くわからない表現です。
Rust 以外の言語での幽霊型
- Haskel のケース
data Phantom x a = Phantom
- Scala のケース
case class Phantom[X, A](a: A)
いずれのケースも、型パラメータ X
が定義はされているものの使われていないのです。ここで何をしようとしているとかというと、実際の値の型になる A
は同だっとしても、X, A
という組み合わせでコンパイルチェックを行い、誤った値を渡した場合にコンパイルエラーにするというもの、らしいです。
まだ正直よく分かりません…
Rust での幽霊型
Rust で実例を見ながら考えたいと思います。
Rust でも同様に使用しない型パラメータの X
と使用している型の A
の組み合わせで構造体を定義します。
#[derive(Debug,PartialEq)]
struct PhantomStruct<X, A> {
value: A
}
この場合、次のようなコンパイルエラーが発生します。
パラメータ
X
は決して使用されません。
X
を削除するか、フィールドで参照するか、あるいはPhantomData
のようなマーカーを使用することを検討してください。
もし、X
を 定数 パラメータにしたい場合は、代わりにconst X: usize
を使用してください。
当然ながら、X
の削除あるいは利用、または PhantomData
を使いなさいとエラーメッセージが出力されています。
以下から、PhantomData
について見てみたいと思います。
おもしろい表現の説明がありました:
T
を所有する ように振る舞う ものをマークするために使用されるゼロサイズのタイプ。
実際には T
型の値を格納していないのにも関わらず、T
型の値を格納しているように動作するようコンパイラに対して支持をするのが、この std::marker::PhantomData
です。
PhantomData
を使用して、次のように構造体を修正します。
use std::marker::PhantomData;
struct PhantomStruct<X, A> {
value: A,
phantom: PhantomData<X>
}
これを次のようにインスタンスを作ります。
それぞれ <i32, char>
と <i64, char>
の型を持つ構造体を作ります。
let _phantom1: PhantomStruct<i32, char> = PhantomStruct {
value: 'P',
phantom: PhantomData
};
let _phantom2: PhantomStruct<i64, char> = PhantomStruct {
value: 'P',
phantom: PhantomData
};
見てもらうと分かるように、実際の値としてはどちらも P
という文字型の値をもっているだけなのですが、これらを比較しようとするとコンパイルエラーになります。
mismatched types
expected structPhantomStruct<i32, _>
found structPhantomStruct<i64, _>
このように、実際としては使用しない型なのですが、文字通り マーカーとして使っている感じですね。
Day 41 のまとめ
さて、今日は Phantom Type、日本語で幽霊型、についてちょっと使い方を見てみました。
最初にも言ったように、今までぼくが使っていた言語で幽霊型と呼ばれるような書式がなかったと思うので、どんなものか、どんな使い方なのか想像ができていませんでした。
型パラメータを使用してコンパイル時には影響を与えるが、実行時の挙動には影響を与えない型を用いたデザインパターンの一種
って言われてもピンとこなかったのですが、実際にコードを書いてみてなんとなく分かった気がしました。
実際には使わない型を型パラメータとして単なるマーカーとして使って、別のものとして区別するってことで分かりました。
あとは、もう少し、実際のユースケースとしての使い方まで理解が深まるようにしたいかなと思います。
Discussion