PhantomData 2/4: 型レベルのタグ付け
型パラメータを追加して、前のスライドの問題を解決しましょう。
// Copyright 2025 Google LLC // SPDX-License-Identifier: Apache-2.0 // use std::marker::PhantomData; pub struct ChatId<T> { id: u64, tag: T } pub struct UserTag; pub struct AdminTag; pub trait ChatUser {/* ... */} pub trait ChatAdmin {/* ... */} impl ChatUser for UserTag {/* ... */} impl ChatUser for AdminTag {/* ... */} // 管理者はユーザーです impl ChatAdmin for AdminTag {/* ... */} // impl <T> Debug for UserTag<T> {/* ... */} // impl <T> PartialEq for UserTag<T> {/* ... */} // impl <T> Eq for UserTag<T> {/* ... */} // 以下同様 ... impl <T: ChatUser> ChatId<T> {/* ユーザー以上のすべての機能 */} impl <T: ChatAdmin> ChatId<T> {/* 管理者専用のすべての機能 */} fn main() {}
-
ここでは型パラメータを使い、異なる権限トレイトを実装する「タグ」型によって権限を制御しています。
タグ型、あるいはマーカー型は、利用者や API 設計者にとって何らかの意味を持つゼロサイズ型です。
-
問い: これをその型の実際のインスタンスにすると、どのような問題が生じますか?
答え: それがゼロサイズ型(
()やstruct MyTag;など)でない場合、私たちが必要としているのはコンパイル時にのみ関係する型情報だけなのに、必要以上のメモリを割り当てることになります。 -
実演:
tagの値を完全に削除してから、コンパイルしてみましょう!これはコンパイルできません。未使用の(phantom)型パラメータがあるためです。
ここで
PhantomDataの出番です! -
実演:
PhantomDataの import のコメントを外し、ChatId<T>を次のようにします。#![allow(unused)] fn main() { // Copyright 2025 Google LLC // SPDX-License-Identifier: Apache-2.0 pub struct ChatId<T> { id: u64, tag: PhantomData<T>, } } -
PhantomData<T>は、型パラメータを持つゼロサイズ型です。これの値は、他の ZST と同様にlet phantom: PhantomData<UserTag> = PhantomData;のように、あるいはPhantomData::default()を使って構築できます。実演:
ChatId<T>に対してFrom<u64>を実装し、PhantomDataの構築を強調します。#![allow(unused)] fn main() { // Copyright 2025 Google LLC // SPDX-License-Identifier: Apache-2.0 impl<T> From<u64> for ChatId<T> { fn from(value: u64) -> Self { ChatId { id: value, // または `PhantomData::default()` tag: PhantomData, } } } } -
PhantomDataは Typestate パターンの一部として使用でき、同じ構造を持ちながら異なるメソッドを持つデータを表現できます。たとえば、TaggedData<Start>には、TaggedData<End>にはないメソッドやトレイト実装を持たせることができます。