暫定。 この章はメモリモデルを定めます。意図的に Rust より単純にしてあり、ライフタイム、借用チェッカー、ムーブはありません。
値セマンティクス
すべての型は値型です。代入、引数の受け渡し、値の返却は値をコピーし、コピーを変更しても元の値は変わりません。実装は記憶領域を共有して書き込み時にコピーしてもかまいませんが、プログラムからそれを観測することはできません。
struct Point { x: i32, y: i32 }
fn demo() -> i32 { let a = Point { x: 1, y: 2 }; let mut b = a; // a copy b.x = 10; a.x // still 1: `a` is unchanged and still usable}ムーブもデストラクタもありません。値は、何もそれを保持しなくなった時点でなくなります。
場所と可変性
場所とは値を保持するものです。ローカル変数、パラメータ、物語変数、それらのフィールドや要素、参照の指す先(*r)がそうです。代入、複合代入、&mut、&mut self レシーバーのメソッドには可変な場所が必要です。
let mutのローカル変数、またはmutパラメータpushdown fnのコードの中の物語変数(static mut)- 可変な場所のフィールドや要素
&mutを通してたどり着いた場所
&T を通してたどり着いた場所、mut のないローカル変数やパラメータ、定数、ハンドルは読み取り専用です。それらを変更すると E3017 です。
参照
&T と &mut T を使うと、値をコピーせずに読んだり変更したりできます。&x と &mut x は場所 x への参照を作り、*r は参照の指す場所です。フィールドアクセスとメソッド呼び出しは自動で参照をたどります。
fn bump(counter: &mut u32) { *counter += 1;}
fn demo() -> u32 { let mut n: u32 = 0; bump(&mut n); let r = &mut n; let s = r; // copies the pointer, not the value *s += 1; n // 2}参照は場所を指す共有ポインタです。参照は指す場所を生かし続け、参照をコピーすると値ではなくポインタがコピーされます。
fnとconst fnのコードでは、参照は普通の値です。ローカル変数に束縛し、フィールドやコレクションに格納し、返し、クロージャでキャプチャできます。- ある参照を通した書き込みは、同じ場所を指す他のすべての参照から見えます。借用チェッカーはなく、同じ場所への
&mutが 2 つ同時に存在してもかまいません。ゲームは VM で動くので、これはプログラムを驚かせることはあってもメモリを壊すことはありません。 &mut Tは暗黙に&Tに変換されます。- 参照に対する
==は指す先の値を比較します。 constアイテム、物語変数、ハンドルの型には参照を含められません(E3012)。
レンダー関数はよくある使い方を示しています。画面は値として渡され、&mut self を取るメソッドで変更され、返されます。
const HANAKO: EntityId = EntityId(216);
const fn hanako_appears(mut screen: Screen) -> Screen { screen.entity(HANAKO).opacity().change(1.0).apply_animation(tween(ms(300))); screen}物語の中の参照
pushdown fn の中では、参照は第二級です。下へ渡すことはできても、保持することはできません。
- 参照型はパラメータの型としてだけ使えます。メソッドのレシーバー(
&self、&mut self)も含みます。ローカル変数、フィールド、キャプチャした変数は参照を保持できません(E3014)。 - 関数が参照を返せるのは、レシーバーが参照である場合だけです。その結果は、それを作った文の終わりまで有効です(
E3014)。 - 排他性。 1 回の呼び出しで
&mutとして渡した場所を他の引数として渡すことはできません。そのため、一方が変更しうる 2 つのパラメータが別名になることはありません(E3016)。
static mut TRUST: Int<0, 5> = 0;static mut DOUBT: Int<0, 5> = 0;
pushdown fn shift(from: &mut Int<0, 5>, to: &mut Int<0, 5>) { *from -= 1; *to += 1;}
pushdown fn reassure() { shift(&mut DOUBT, &mut TRUST); // fine shift(&mut TRUST, &mut TRUST); // E3016: the same place twice}参照は渡された呼び出しより長く生きることがなく、他の引数と別名になることもないので、&mut x を取る pushdown fn は、x のコピーを受け取り、戻るときに呼び出し側が結果を書き戻すのとまったく同じに振る舞います。pushdown fn の &mut 引数は有限型の入出力パラメータであり、物語の状態がポインタを保持することはありません。これにより参照は物語の解析にとって無害になります。
クロージャ
fn scaled(values: [i32; 3], factor: i32) -> [i32; 3] { let scale = |v: i32| v * factor; // captures a copy of `factor` let clamp = |v: i32| -> i32 { if v > 100 { 100 } else { v } }; [clamp(scale(values[0])), clamp(scale(values[1])), clamp(scale(values[2]))]}クロージャは |parameters| body と書きます。文脈から型がわかるときはパラメータの型を省略できます。戻り値型 -> R を明示する場合、本体はブロックでなければなりません。move はありません。
クロージャは値でキャプチャします。作られた時点で、使うローカル変数をコピーします。クロージャの中でキャプチャしたコピーを変更しても元の変数は変わりません。共有された可変状態は、参照、物語変数、シグナルを通して扱います。参照をキャプチャするとポインタがコピーされるので、それを通した書き込みは元の場所に届きます。
クロージャは関数と同じく種別を持ちます。その種別は、渡される先や格納先のクロージャ型の種別です。それがなければ、クロージャを含む関数の種別です。本体はその種別の規則で検査されます。クロージャで何ができるかは、それを作るコードの種別で決まります。
| 種別 | クロージャは | できること |
|---|---|---|
fn | 第一級 | 格納、返却、UI の値(ハンドラ)への組み込み |
const fn | 第一級 | 格納、返却 |
pushdown fn | 第二級 | 引数として渡して呼ぶこと。格納、返却、他のクロージャによるキャプチャはできません(E3015) |
物語の中のクロージャは即時手続きです。ステップを実行することも(E3002)、シーンを呼ぶことも(E3003)できず、loop や while も持てません(E3004)。
static mut AFFECTION_MIO: Int<0, 5> = 0;
pushdown fn twice(f: pushdown Fn()) { f(); f();}
#[scene]pushdown fn Compliment() { twice(|| AFFECTION_MIO += 1); s!(t!["Mio smiles."]);}クロージャは決して Eq ではありません(E2017)。
物語の中のクロージャが第二級である理由
物語の中のクロージャが第一級だと、物語の状態が、クロージャを含むクロージャを際限なく含めるようになります。これは高階プッシュダウンシステムです。依然として決定可能ですが、解析のコストは階数が 1 つ上がるごとに指数的に増えます(Ong, On model-checking trees generated by higher-order recursion schemes, LICS 2006)。第二級のクロージャは、関数と、呼び出しが戻ると消える有限のキャプチャ値の組にすぎないので、物語は普通のプッシュダウンシステムのままです。
クロージャ型とポインタ型
関数ポインタ型とクロージャ型は、呼び出すコードの種別を持ちます。
| 型 | 保持するもの |
|---|---|
fn(A) -> R | 名前付きの fn |
const fn(A) -> R | 名前付きの const fn、またはタプル構造体やタプルバリアントのコンストラクタ |
pushdown fn(A) -> R | 名前付きの即時手続き |
scene fn(A) -> R | 名前付きのシーン(構文は暫定。scene がキーワードになるのはここだけです) |
Fn(A) -> R、const Fn(A) -> R、pushdown Fn(A) -> R | その種別のクロージャまたは関数(構文は暫定) |
種別は完全に一致しなければなりません。const fn は fn(A) -> R 型の値ではなく、シーンは pushdown fn(A) -> R 型の値ではありません。次の変換は暗黙に行われます。
- 名前付き関数やコンストラクタは、その種別の関数ポインタに変換されます。
- 名前付き関数や関数ポインタは、同じ種別のクロージャ型に変換されます。シーンにはクロージャ型がありません。
- 何もキャプチャしないクロージャは、同じ種別の関数ポインタに変換されます。レンダー関数をその場で書くのはこの仕組みです(インラインのレンダー関数を参照)。変数をキャプチャするクロージャは変換できません(
E2036)。
const fn double(x: u32) -> u32 { x * 2 }
fn demo() -> Option<u32> { let f: const fn(u32) -> u32 = double; let wrap: const fn(u32) -> Option<u32> = Some; // a variant constructor let g: const Fn(u32) -> u32 = f; // pointer to closure type wrap(g(f(1)))}関数ポインタは有限集合(プログラムの名前付き関数)の値なので、pushdown fn、scene fn、const fn のポインタは有限型であり、物語が保持できます。関数ポインタは同一性で比較されます。
ポインタやクロージャを通した呼び出しも、直接の呼び出しと同じ呼び出し規則に従います。型が呼び出し先の種別をコンパイラに伝えるからです。