ブランド付き型の実装 (ブランド化 3/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 { debug_assert!(ix.0 < self.0.len()); unsafe { *self.0.get_unchecked(ix.0) } } }
-
動機: ある型に対して「証明済みインデックス」を持たせたい一方で、それらの インデックスが同じ型の別の変数で使えてしまってほしくありません。また、それらの インデックスがスコープの外へ逃げてしまうことも避けたいです。
今回のブランド付き型は
Bytes: バイト配列です。今回のブランド付きトークンは
ProvenIndex: 範囲内であることが分かっている インデックスです。 -
この実装には注目すべき点がいくつかあります:
newはBytesを返さず、代わりに「開始データ」と、一度だけ使われ、 呼び出されたときにBytesが渡されるクロージャを受け取ります。- その
new関数は、トレイト境界にfor<'a>を持っています。 - インデックスを取得する getter と、証明済みインデックスで値を取得する getter の両方があります。
-
問い: なぜ
newはBytesを返さないのでしょうか?答え:
Bytesには API が制御する一意なライフタイムを持たせる必要があるからです。 -
問い: では、
new()がBytesを返したらどうなるのでしょうか? 具体的にはどのような問題が 生じますか?答え: 仮にその
new()メソッドがあるとして、そのシグネチャを考えてみてください:fn new<'a>() -> Bytes<'a> { ... }これでは API の利用者がライフタイム
'aを何にするか選べてしまい、異なるBytesのインスタンス間のライフタイムが一意であり、互いにサブタイプ化 できないことを保証する能力が失われます。 -
問い: なぜ
get_indexとget_provenの両方が必要なのでしょうか?想定される答え: 「あるインデックスが有効かどうかはコンパイル時には分からないから」
問い: では、証明済みインデックスにはどんな意味があるのでしょうか?
答え: 境界チェックを避けつつ、どのインデックスが有効かという知識を個々の 変数ごとに保持し、誤って別の変数に対して使えないようにするためです。
注: 焦点は境界チェックの過剰な使用を避けることだけではなく、インデックスが 別の変数へ「またがって」使われるのを防ぐことにもあります。