PhantomData 2/4: Type-level tagging
Let’s solve the problem from the previous slide by adding a type parameter.
// 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 {/* ... */} // Admins are users
impl ChatAdmin for AdminTag {/* ... */}
// impl <T> Debug for UserTag<T> {/* ... */}
// impl <T> PartialEq for UserTag<T> {/* ... */}
// impl <T> Eq for UserTag<T> {/* ... */}
// And so on ...
impl <T: ChatUser> ChatId<T> {/* All functionality for users and above */}
impl <T: ChatAdmin> ChatId<T> {/* All functionality for only admins */}
fn main() {}
-
Here we’re using a type parameter and gating permissions behind “tag” types that implement different permission traits.
Tag types, or marker types, are zero-sized types that have some semantic meaning to users and API designers.
-
Ask: What issues does having it be an actual instance of that type pose?
Answer: If it’s not a zero-sized type (like
()orstruct MyTag;), then we’re allocating more memory than we need to when all we care for is type information that is only relevant at compile-time. -
Demonstrate: remove the
tagvalue entirely, then compile!This won’t compile, as there’s an unused (phantom) type parameter.
This is where
PhantomDatacomes in! -
Demonstrate: Uncomment the
PhantomDataimport, and makeChatId<T>the following:#![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>is a zero-sized type with a type parameter. We can construct values of it like other ZSTs withlet phantom: PhantomData<UserTag> = PhantomData;or with thePhantomData::default()implementation.Demonstrate: implement
From<u64>forChatId<T>, emphasizing the construction ofPhantomData#![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, // Or `PhantomData::default()` tag: PhantomData, } } } } -
PhantomDatacan be used as part of the Typestate pattern to have data with the same structure but different methods, e.g., haveTaggedData<Start>implement methods or trait implementations thatTaggedData<End>doesn’t.