Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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>.