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 4/4)

// 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 {
        self.0[ix.0]
    }
}

fn main() {
    let result = Bytes::new(vec![4, 5, 1], |mut bytes_1| {
        Bytes::new(vec![4, 2], |mut bytes_2| {
            let index_1 = bytes_1.get_index(2).unwrap();
            let index_2 = bytes_2.get_index(1).unwrap();
            bytes_1.get_proven(&index_1);
            bytes_2.get_proven(&index_2);
            // bytes_2.get_proven(&index_1); // ❌🔨
            "Computations done!"
        })
    });
    println!("{result}");
}
  • Реализация готова, теперь можно написать программу, в которой типы-токены, служащие доказательством существующих индексов, нельзя передавать между переменными.

  • Демонстрация: раскомментируйте строку bytes_2.get_proven(&index_1); и покажите, что она не компилируется, когда мы используем индексы из разных переменных.

  • Спросите: какие операции мы можем выполнить, чтобы гарантированно получить подтверждённый индекс?

    Ожидаемый ответ: реализация «push». Предлагаемая демонстрация:

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn push(&mut self, value: u8) -> ProvenIndex<'id> {
        self.0.push(value);
        ProvenIndex(self.0.len() - 1, InvariantLifetime::default())
    }
    }
  • Спросите: можно ли сделать это не только для массива байтов, а как общую обёртку над Vec<T>?

    Несложно: да!

    Возможно, стоит продемонстрировать обобщение Bytes<'id> до BrandedVec<'id, T>.

  • Спросите: в каких других областях можно применить что-то подобное?

  • Получившийся API токенов очень строгий, но то, что он позволяет доказать безопасным в системе типов Rust, имеет большое значение.

Для дальнейшего изучения

  • GhostCell — структура, которая позволяет создавать безопасные циклические структуры данных в Rust (наряду с другими структурами, которые раньше было трудно выразить). Она использует такой тип-токен, чтобы ячейки не могли «выйти» за пределы контекста, в котором операции, подобные показанным в этих примерах, безопасны.

    Эта серия слайдов «Branded Types» основана на реализации BrandedVec из статьи, где многие детали реализации для этого случая рассмотрены подробнее. Это мягкое введение в то, как сам GhostCell реализован и используется на практике.

    GhostCell также использует формальные проверки вне системы типов Rust, чтобы доказать, что то, что он разрешает в таком контексте (брендирование времён жизни), безопасно.