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и скомпилируйте!Это не скомпилируется, так как есть неиспользуемый (фантомный) параметр типа.
Здесь на помощь приходит
PhantomData! -
Продемонстрируйте: раскомментируйте импорт
PhantomDataи сделайте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().Продемонстрируйте: реализуйте
From<u64>дляChatId<T>, обратив внимание на создание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>.