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, чтобы доказать, что то, что он разрешает в таком контексте (брендирование времён жизни), безопасно.