Реализация 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?Ожидаемый ответ: «потому что на этапе компиляции нельзя узнать, находится ли индекс в границах».
Спросите: тогда в чём смысл подтверждённых индексов?
Ответ: избежать проверок границ, сохранив знание о том, какие индексы заняты, привязанным к конкретным переменным, чтобы их нельзя было ошибочно использовать с другой.
Примечание: акцент делается не только на том, чтобы не злоупотреблять проверками границ, но и на предотвращении «перекрёстного» использования индексов.