借用チェッカーを使って不変条件を強制する

借用チェッカーは、メモリの所有権を強制するために追加されたものですが、他の問題をモデル化し、API の誤用を防ぐこともできます。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// ドアは開いているか閉じているかのどちらかで、施錠または解錠するには正しい鍵が必要です。
/// これは Shared な鍵と Owned なドアでモデル化されています。
pub struct DoorKey {
    pub key_shape: u32,
}
pub struct LockedDoor {
    lock_shape: u32,
}
pub struct OpenDoor {
    lock_shape: u32,
}

fn open_door(key: &DoorKey, door: LockedDoor) -> Result<OpenDoor, LockedDoor> {
    if door.lock_shape == key.key_shape {
        Ok(OpenDoor { lock_shape: door.lock_shape })
    } else {
        Err(door)
    }
}

fn close_door(key: &DoorKey, door: OpenDoor) -> Result<LockedDoor, OpenDoor> {
    if door.lock_shape == key.key_shape {
        Ok(LockedDoor { lock_shape: door.lock_shape })
    } else {
        Err(door)
    }
}

fn main() {
    let key = DoorKey { key_shape: 7 };
    let closed_door = LockedDoor { lock_shape: 7 };
    let opened_door = open_door(&key, closed_door);
    if let Ok(opened_door) = opened_door {
        println!("鍵の形状 '{}' でドアを開けました", key.key_shape);
    } else {
        eprintln!(
            "ドアは開きませんでした!あなたの鍵で開けられるのは形状 '{}' の錠前だけです",
            key.key_shape
        );
    }
}
  • 借用チェッカーがメモリ安全性のバグ(解放後の使用、データ競合)を防ぐのは、すでに見てきました。

  • 型を使って API を形作り、制限する方法も、 Typestate パターン ですでに使っています。

  • 言語機能は、しばしば特定の目的のために導入されます。

    時間が経つにつれて、ユーザーは、導入時には予想されていなかった方法でその機能を使うやり方を生み出すことがあります。

    Java 5 は 2004 年に Generics を導入しました。その 主な公称目的は型安全なコレクションを可能にすること でした。

    当初、採用はゆっくりでしたが、一部の新しいプロジェクトでは最初からジェネリクスを中心に API を設計し始めました。

    それ以来、ユーザーと言語開発者は、ジェネリクスの用途を型安全な API 設計の他の領域へと広げてきました。

    • クラス情報は、Java の Class<T> や Guava の TypeToken<T> を通じて保持できます。
    • Builder パターンは、再帰的ジェネリクスを使って実装できます。 ここで目指すのも似たようなことです。借用チェッカーは解放後の使用やデータ競合を防ぐために導入されましたが、ここではそれを API 設計のための別のツールの 1 つとして扱います。 これは、メモリ安全性のバグを防ぐこととは関係のないプログラムの性質をモデル化するためにも使えます。
  • 借用チェッカーを問題解決のツールとして使うには、その本来の目的が、解放後の使用やデータ競合を防ぐ文脈で可変エイリアスを防止することだという点を、いったん「忘れる」必要があります。

    ルールは同じでも意味が少し異なる状況の中で作業していると考えるべきです。

  • この例では、所有権と借用を使って物理的なドアの状態をモデル化します。

    open_doorLockedDoor消費 し、新しい OpenDoor を返します。古い LockedDoor の値はもう使えません。

    間違った鍵が使われた場合、ドアは施錠されたままです。その場合、ResultErr として返されます。

    すでに開けたドアを使おうとすると、コンパイル時エラーになります。

  • 同様に、lock_doorOpenDoor を消費するため、ドアを 2 回閉じることをコンパイル時に防ぎます。

  • 借用チェッカーのルールはメモリ安全性のバグを防ぐために存在しますが、その根底にある論理システムはメモリが何であるかを「知って」いるわけではありません。

    借用チェッカーがしていることは、ユーザーが操作をどのような順序で行えるかについての特定のルール集合を強制することだけです。

    これは、誤用しにくく、あるいは誤用不可能な API を設計するために、借用チェッカーのルールを活用する 1 つの例にすぎません。