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

Реализация Branded-типов (Branding 3/4)

Создание branded-типов отличается от создания обычных типов.

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomData;

#[derive(Default)]
struct InvariantLifetime<'id>(PhantomData<*mut &'id ()>);
struct ProvenIndex<'id>(usize, InvariantLifetime<'id>);

struct Bytes<'id>(Vec<u8>, InvariantLifetime<'id>);

impl<'id> Bytes<'id> {
    fn new<T>(
        // Данные, которые мы хотим изменять в этом контексте.
        bytes: Vec<u8>,
        // Функция, которая уникально помечает время жизни `Bytes`
        f: impl for<'a> FnOnce(Bytes<'a>) -> T,
    ) -> T {
        f(Bytes(bytes, InvariantLifetime::default()),)
    }

    fn get_index(&self, ix: usize) -> Option<ProvenIndex<'id>> {
        if ix < self.0.len() { Some(ProvenIndex(ix, InvariantLifetime::default())) }
        else { None }
    }

    fn get_proven(&self, ix: &ProvenIndex<'id>) -> u8 {
        debug_assert!(ix.0 < self.0.len());
        unsafe { *self.0.get_unchecked(ix.0) }
    }
}
  • Мотивация: мы хотим иметь «подтверждённые индексы» для типа и не хотим, чтобы эти индексы могли использоваться разными переменными того же типа. Также мы не хотим, чтобы эти индексы выходили за пределы области видимости.

    Нашим branded-типом будет Bytes: массив байтов.

    Нашим branded-токеном будет ProvenIndex: индекс, заведомо находящийся в допустимом диапазоне.

  • В этой реализации есть несколько важных частей:

    • new не возвращает Bytes; вместо этого он принимает «исходные данные» и одноразовое замыкание, которое получает Bytes при вызове.
    • У этой функции new в ограничении трейта есть for<'a>.
    • У нас есть и геттер для индекса, и геттер для значения по подтверждённому индексу.
  • Спросите: почему new не возвращает Bytes?

    Ответ: потому что нам нужно, чтобы у Bytes было уникальное время жизни, которым управляет API.

  • Спросите: а что, если бы new() возвращал Bytes? Какой конкретный вред это принесло бы?

    Ответ: подумайте о сигнатуре этого гипотетического метода new():

    fn new<'a>() -> Bytes<'a> { ... }

    Это позволило бы пользователю API выбрать, каким будет время жизни 'a, и лишило бы нас возможности гарантировать, что времена жизни разных экземпляров Bytes уникальны и не могут быть подтипизированы друг к другу.

  • Спросите: зачем нужны и get_index, и get_proven?

    Ожидаемый ответ: «потому что на этапе компиляции нельзя узнать, находится ли индекс в границах».

    Спросите: тогда в чём смысл подтверждённых индексов?

    Ответ: избежать проверок границ, сохранив знание о том, какие индексы заняты, привязанным к конкретным переменным, чтобы их нельзя было ошибочно использовать с другой.

    Примечание: акцент делается не только на том, чтобы не злоупотреблять проверками границ, но и на предотвращении «перекрёстного» использования индексов.