所有権とスコープのセクションでは、値が関数に渡されるとき、その値は関数のスコープに移動することを説明しました。これは、関数がその値の所有者となり、元のスコープ(所有者)はもはやそれを使用できなくなることを意味します。これはMoveの重要な概念であり、値が同時に複数の場所で使用されないことを保証します。しかし、値を関数に渡しつつも所有権を保持したい場合があります。ここで参照が役立ちます。

これを説明するために、メトロ(地下鉄)パスのシンプルな例を考えてみましょう。以下の4つのシナリオを見ていきます:

  1. カードは固定価格でキオスクで購入できる
  2. カードは検査員に見せて、乗客が有効なパスを持っていることを証明できる
  3. カードは改札で使用してメトロに入場し、乗車回数を消費できる
  4. カードは空になったらリサイクルできる

レイアウト

メトロパスアプリケーションの初期レイアウトはシンプルです。Card型と、1枚のカードの乗車回数を表すUSES定数を定義します。また、カードが空の場合のエラー定数も追加します。

module book::metro_pass {
    /// カードが空の場合のエラーコード。
    const ENoUses: u64 = 0;

    /// メトロパスカードの使用回数。
    const USES: u8 = 3;

    /// メトロパスカード
    public struct Card { uses: u8 }

    /// メトロパスカードを購入する。
    public fun purchase(/* Coinを渡す */): Card {
        Card { uses: USES }
    }
}

参照

参照は、所有権を放棄せずに値を関数に見せる方法です。この場合、カードを検査員に見せるとき、所有権を放棄したくありませんし、乗車回数を消費させたくもありません。単にカードの値を読み取ることと、その所有権を証明することを許可したいだけです。

そのために、関数のシグネチャで&記号を使用して、値自体ではなく値への参照を渡していることを示します。

    /// メトロパスカードを検査員に見せる。
    public fun is_valid(card: &Card): bool {
        card.uses > 0
    }

これで関数はカードの所有権を取得できず、乗車回数を消費することもできません。しかし、その値を読み取ることはできます。注目すべきは、このようなシグネチャでは、カードなしで関数を呼び出すことが不可能になることです。これは、後の章で説明する能力パターンを可能にする重要な特性です。

可変参照

場合によっては、関数がカードの値を変更することを許可したいことがあります。例えば、改札でカードを使用するとき、乗車回数を消費したいです。これを実装するために、関数のシグネチャで&mutキーワードを使用します。

    /// 改札でメトロパスカードを使用してメトロに入場する。
    public fun enter_metro(card: &mut Card) {
        assert!(card.uses > 0, ENoUses);
        card.uses = card.uses - 1;
    }

関数本体で見られるように、&mut参照は値の変更を許可し、関数は乗車回数を消費することができます。

値渡し

最後に、値自体を関数に渡す場合の例を示します。この場合、関数は値の所有権を取得し、元のスコープはもはやそれを使用できなくなります。カードの所有者はそれをリサイクルし、したがって所有権を失うことができます。

    /// メトロパスカードをリサイクルする。
    public fun recycle(card: Card) {
        assert!(card.uses == 0, ENoUses);
        let Card { uses: _ } = card;
    }

recycle関数では、カードは値で取得され、アンパックして破棄することができます。元のスコープはもはやそれを使用できません。

完全な例

アプリケーションの完全なフローを説明するために、すべての部分をテストにまとめてみましょう。