相互排他的な参照 / 「Aliasing XOR Mutability」

&T 参照と &mut T 参照の相互排他性を利用すると、準備が整う前に データが使われるのを防げます。

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

pub struct QueryResult;
pub struct DatabaseConnection {/* フィールドは省略 */}

impl DatabaseConnection {
    pub fn new() -> Self {
        Self {}
    }
    pub fn results(&self) -> &[QueryResult] {
        &[] // ダミーの結果
    }
}

pub struct Transaction<'a> {
    connection: &'a mut DatabaseConnection,
}

impl<'a> Transaction<'a> {
    pub fn new(connection: &'a mut DatabaseConnection) -> Self {
        Self { connection }
    }
    pub fn query(&mut self, _query: &str) {
        // クエリを送信するが、結果は待たない。
    }
    pub fn commit(self) {
        // トランザクションの実行を完了し、結果を取得する。
    }
}

fn main() {
    let mut db = DatabaseConnection::new();

    // トランザクション `tx` は `db` を可変借用する。
    let mut tx = Transaction::new(&mut db);
    tx.query("SELECT * FROM users");

    // `db` はすでに `tx` によって可変借用されているため、これはコンパイルされない。
    // let results = db.results(); // ❌🔨

    // `tx` が `commit()` によって消費されると、`db` の借用は終了する。
    tx.commit();

    // これで `db` を再び借用できる。
    let results = db.results();
}
  • 動機: このデータベース API では、クエリは非同期実行に向けて開始され、 結果が利用可能になるのはトランザクション全体が完了してからです。

    ユーザーはクエリが即座に実行されると考えて、利用可能になる前に結果を 読み取ろうとするかもしれません。この API の誤用により、アプリが 不完全または不正確なデータを読み取ってしまう可能性があります。

    一見すると明らかな誤解ですが、このような状況は実際に起こりえます。

    問いかけ: 適切な使い方についてドキュメントを読まなかったために、API を 誤解したことはありますか?

    想定: キャリア初期や大学在学中にしたミスや誤解の例。

    API の規模とユーザー数が増えるにつれて、その API が表現するシステムに ついて深い知識を持つユーザーの割合は小さくなります。

  • この例は、この種の誤用を防ぐために Aliasing XOR Mutability をどのように 利用できるかを示しています。

  • プログラマーが、クエリは非同期実行として開始されるのではなく即座に 実行されると考えてしまうと、このコードは結果の準備が整う前に 読み取ってしまう可能性があります。

  • Transaction 型のコンストラクターはデータベース接続への可変参照を 受け取り、それを返される Transaction 値の中に保存します。

    ここで明示されているライフタイムに気後れする必要はなく、この場合は単に 「Transaction よりも、それに渡された DatabaseConnection のほうが 長生きする」ことを意味します。

    この参照が可変なのは、さらにトランザクションを開始したり結果を 読み取ったりするといったほかの用途で DatabaseConnection を 使えないようにするためです。

  • Transaction が存在している間は、その Transaction の作成元となった DatabaseConnection 変数には触れられません。

    実演: db.results() の行をコメント解除してください。そうすると、db は すでに可変借用されているため、コンパイルエラーになります。

  • 注: クエリ結果を公開せず、ゲッター関数の背後に置くことで、 「アクティブなトランザクションが存在しない場合にのみ、ユーザーは クエリ結果を確認できる」という不変条件を強制できます。

    クエリ結果が構造体の公開フィールドに置かれていた場合、この不変条件は 破られうるでしょう。