参照
Moveには2種類の参照があります:不変の&
と可変の&mut
です。不変参照は読み取り専用で、基となる値(またはその任意のフィールド)を変更出来ません。可変参照では、その参照の書き込みを介して変更が可能です。Moveの型システムは、参照エラーを防ぐ所有権の規則を強制します。
参照のルール詳細は、構造体とリソースを御覧下さい。
参照演算子
Moveは、参照を作成および拡張するだけでなく、可変参照を不変参照へ変換する演算子を提供します。ここでも他の箇所でも「式e
がT
型を持つ」場合e: T
という表記を使用します。
構文 | 型 | 説明 |
---|---|---|
&e | e: T である&T 及びT は非参照型 ???? | e への不変参照を作成 |
&mut e | e: T である&mut T 及びT は非参照型 | e への可変参照を作成 |
&e.f | e.f: T である&T | 構造体e のフィールドf への不変参照を作成 |
&mut e.f | e.f: T である&mut T | 構造体e のフィールドf への可変参照を作成 |
freeze(e) | e: &mut T である&T | Convert 可変参照e を不変参照へ変換する |
&e.f
と&mut e.f
演算子は、構造体への新しい参照を作成する場合と、既存の参照を拡張する場合のどちらでも使用出来ます。
script {
fun example() {
let s = S { f: 10 };
let f_ref1: &u64 = &s.f; // 動作します
let s_ref: &S = &s;
let f_ref2: &u64 = &s_ref.f // 動作します
}
}
複数のフィールドを持つ参照式は、両方の構造体が同じモジュール内にある限り作動します。
module 0x42::example {
struct A { b: B }
struct B { c : u64 }
fun f(a: &A): &u64 {
&a.b.c
}
}
最後に、参照への参照は許可されません。
script {
fun example() {
let x = 7;
let y: &u64 = &x;
let z: &&u64 = &y; // コンパイルされません
}
}
参照の読み取りと書き込み
可変参照と不変参照の両方を読み取って、参照値のコピーを作成出来ます。
書き込む事が出来るのは可変参照のみです。*x = v
の書き込みでは、以前x
に格納されていた値が破棄されv
で更新されます。
どちらの操作も C のような*
構文を使用します。ただし、読み取りは式であるのに対し、書き込みは変更です。この変更は等号(イコール)の左側で発生する必要が有ります。注意して下さい。
構文 | 型 | 説明 |
---|---|---|
*e | e が&T もしくは&mut T であるT | e が指す値を読み取る |
*e1 = e2 | e1: &mut T 及びe2: T が() である | e2 でe1 の値を更新する |
参照を読み取るには、基となる型が参照を読み取る時、値の新しいコピーを作成するcopy
機能を持っている必要が有ります。このルールがリソース値のコピーを防止します。
module 0x42::coin {
struct Coin {} // コピーが有りません
fun copy_resource_via_ref_bad(c: Coin) {
let c_ref = &c;
let counterfeit: Coin = *c_ref; // 許可されません!
pay(c);
pay(counterfeit);
}
}
二重の意味: 参照へ書き込むためには、基となる型は、 参照に書き込むと古い値が破棄(または「ドロップ」)されるdrop
機能が必要です。このルールは、リソース値の破壊を防止します。
module 0x42::coin {
struct Coin {} // ドロップが有りません
fun destroy_resource_via_ref_bad(ten_coins: Coin, c: Coin) {
let ref = &mut ten_coins;
*ref = c; // 許可されていません--10コインが破棄されてしまします!
}
}
freeze
推論
可変参照は、不変参照が予測されるコンテキストで使用できます。
script {
fun example() {
let x = 7;
let y: &u64 = &mut x;
}
}
これが機能するのは、内部でコンパイラが必要な場所へfreeze
命令を挿入するためです。freeze
推論の実例をいくつか示します。
module 0x42::example {
fun takes_immut_returns_immut(x: &u64): &u64 { x }
// 戻り値の推論を凍結する
fun takes_mut_returns_immut(x: &mut u64): &u64 { x }
fun expression_examples() {
let x = 0;
let y = 0;
takes_immut_returns_immut(&x); // 推論無し
takes_immut_returns_immut(&mut x); // i推論された凍結(&mut x)
takes_mut_returns_immut(&mut x); // 推論無し
assert!(&x == &mut y, 42); // 推論された凍結(&mut y)
}
fun assignment_examples() {
let x = 0;
let y = 0;
let imm_ref: &u64 = &x;
imm_ref = &x; // 推論無し
imm_ref = &mut y; // 推論された凍結(&mut y)
}
}
サブタイプ
このfreeze
推論で、Move型チェッカーは&mut T
を &T
のサブタイプとして見る(View)事が出来ます。上記のように&T
値が使用される任意の式で&mut T
値も使用出来る事を意味します。この用語は、エラーメッセージで使用され、&T
が供給された場所で&mut T
が必要であることを簡潔に示します。例えば
module 0x42::example {
fun read_and_assign(store: &mut u64, new_value: &u64) {
*store = *new_value
}
fun subtype_examples() {
let x: &u64 = &0;
let y: &mut u64 = &mut 1;
x = &mut 1; // 有効
y = &2; // 無効!
read_and_assign(y, x); // 有効
read_and_assign(x, y); // 無効!
}
}
以下のエラーメッセージが生産されます
error:
┌── example.move:12:9 ───
│
12 │ y = &2; // 無効!
│ ^ local 'y'への配置が無効です
·
12 │ y = &2; // 無効!
│ -- 型: '&{integer}'
·
9 │ let y: &mut u64 = &mut 1;
│ -------- これは'&mut u64'のサブタイプでは有りません
│
error:
┌── example.move:15:9 ───
│
15 │ read_and_assign(x, y); // 無効!
│ ^^^^^^^^^^^^^^^^^^^^^ '0x42::example::read_and_assign'の呼び出しが無効です。 パラメーター'store'の引数が無効です
·
8 │ let x: &u64 = &0;
│ ---- 型: '&u64'
·
3 │ fun read_and_assign(store: &mut u64, new_value: &u64) {
│ --------これは'&mut u64'のサブタイプでは有りません
│
現在サブタイプを持つ他の型はタプルのみです。
所有権
可変参照と不変参照はどちらも、同じ参照のコピーや拡張が既に存在する場合でも、常にコピー及び拡張出来ます。
script {
fun reference_copies(s: &mut S) {
let s_copy1 = s; // ok
let s_extension = &mut s.f; // これも ok
let s_copy2 = s; // まだ ok
...
}
}
上記のコードを拒否するRustの所有権システムに精通しているプログラマーにとって、これは驚くべき事かもしれません。Moveの型システムはコピーの扱いに関してはもっと寛容ですが、書き込み前の可変参照の一意の確実な所有権の厳格さは同じです。
参照は保存出来ない
参 照とタプルは、構造体のフィールド値として保存出来ない唯一の型であり、グローバルストレージへ存在出来ないという事でもあります。プログラム実行中に作成された全ての参照は、Moveプログラムが終了すると破棄されます。これらは完全に一時的な物です。この不変は、保存
機能のない型の値でもtrueですが、参照とタプルは、そもそも構造体で許可されない点で一歩進んでいます。
これはMoveとRustのもうひとつの違いであり、参照を構造体内へ保存出来ます。
現在、Moveは参照をシリアル化出来ないためこれをサポート出来ませんが、全てのMove値はシリアル化可能である必要があります。
この要件はMoveの永続的なグローバルストレージに由来します。これはプログラムの実行間で値を永続化するため値をシリアル化する必要が有ります。構造体はグローバルストレージに書き込む事が出来るため、シリアル化可能である必要があります。
参照を構造体に保存出来、それらの構造体がグローバルストレージへ存在する事を禁止する、等という型システムを妄想する事も出来ます。store
機能を持たない構造体内の参照を許可する事は多分出来ますが、それでは問題は完全には解決されません。Moveには静的参照の安全性を追跡するかなり複雑なシステムがあり、この型システムの側面も、構造体内で参照を保存するサポートを拡張する必要が有ります。要は、Moveの型システム(特に参照の安全性に関する側面)を拡 張し、保存された参照をサポートする必要が有ります。これは言語が進化で私たちが注目している物です。s