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> にはないメソッドやトレイト実装を持たせることができます。