所有権とスコープのセクションでは、値が関数に渡されるとき、その値は関数のスコープに移動することを説明しました。これは、関数がその値の所有者となり、元のスコープ(所有者)はもはやそれを使用できなくなることを意味します。これはMoveの重要な概念であり、値が同時に複数の場所で使用されないことを保証します。しかし、値を関数に渡しつつも所有権を保持したい場合があります。ここで参照が役立ちます。
これを説明するために、メトロ(地下鉄)パスのシンプルな例を考えてみましょう。以下の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
関数では、カードは値で取得され、アンパックして破棄することができます。元のスコープはもはやそれを使用できません。
アプリケーションの完全なフローを説明するために、すべての部分をテストにまとめてみましょう。