変数固有のトークン(ブランディング 1/4)

トークンを特定の変数に結び付けたい場合はどうすればよいでしょうか?

// 著作権 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Bytes {
    bytes: Vec<u8>,
}
struct ProvenIndex(usize);

impl Bytes {
    fn get_index(&self, ix: usize) -> Option<ProvenIndex> {
        if ix < self.bytes.len() { Some(ProvenIndex(ix)) } else { None }
    }
    fn get_proven(&self, token: &ProvenIndex) -> u8 {
        unsafe { *self.bytes.get_unchecked(token.0) }
    }
}

fn main() {
    let data_1 = Bytes { bytes: vec![0, 1, 2] };
    if let Some(token_1) = data_1.get_index(2) {
        data_1.get_proven(&token_1); // 問題なく動作します!

        // let data_2 = Bytes { bytes: vec![0, 1] };
        // data_2.get_proven(&token_1); // panic します! これを防げるでしょうか?
    }
}
  • コード内の_特定の変数_にトークンを結び付けたい場合はどうでしょうか? Rust の型 システムでこれを実現できるでしょうか?

  • 動機: バイト配列に対する、既知で有効なインデックスを表すトークン型を持ちたい のです。

    これらの証明済みインデックスがあれば、トークンが_存在するインデックスの証明_と して機能するため、境界チェックを完全に回避できます。

    インデックスが有効であることは分かっているので、get_proven() は境界チェック を省略できます。

  • この例では、ある配列の証明済みインデックスが別の配列で使われるのを防ぐ仕組み がありません。この場合にインデックスが範囲外であれば、それは未定義動作に なります。

  • 実演: data_2.get_proven(&token_1); の行のコメントアウトを外します。

    ここでコードは panic します! インデックス用トークン型におけるこの 「クロスオーバー」をコンパイル時に防ぎたいのです。

  • 問いかけ: これを実現するには、どのような方法が考えられるでしょうか?

    受講者がここから良い実装にたどり着けないことは想定されますが、実験したり、 提案に沿って試し進めたりすることには前向きでいてください。

  • 問いかけ: 代替案には何があり、なぜそれでは十分でないのでしょうか?

    特に Vec::getBytes::get_index のどちらもすでに実行時チェックを 使っているため、インデックス境界の実行時チェックという案が出てくるはずです。

    実行時の境界チェックでは、そもそもこの誤ったクロスオーバーを防げず、 panic が起きることが保証されるだけです。

  • ここで行うこの種のトークン関連付けは、ブランディングと呼ばれます。これは、 より多くの API 設計にトークン型を適用できるようにする高度なテクニック です。

  • GhostCell はこの 手法の著名な利用例であり、後のスライドで触れます。