15_Cairo1.0 中の Snapshot と参照#
この記事で使用されている Cairo コンパイラのバージョン:2.0.0-rc0。Cairo は急速に更新されているため、異なるバージョンの構文にはわずかな違いがあり、将来的には安定したバージョンに記事の内容を更新する予定です。
14_Cairo1.0 変数所有権の記事では、Copy トレイトについて言及しました。Copy トレイトを実装したオブジェクトは、関数に渡される際に自動的に変数をコピーし、コピーを関数に渡します。また、変数が Copy トレイトを実装していない場合、関数に渡される際に move 操作が発生します。しかし、プログラミングの過程で、変数の所有権をコンテキスト内で保持したいが、コピー操作は行いたくない場合、snapshot と reference を使用することができます。
Snapshot#
Snapshot はある時点での変数の値であり、不変性と読み取り専用の 2 つの特性を持っているため、関数にパラメータを渡す際に使用することができます(契約内の view 関数のようなものです)。
基本的な使い方#
Cairo では、スナップショットを使用するために @
演算子と *
演算子が使用されます。@はスナップショットを取得するために使用され、* はスナップショットの値を読み取るために使用されます。例を見てみましょう:
use debug::PrintTrait;
#[derive(Drop, Copy)]
struct Student {
name: felt252,
age: u8,
}
fn print_student(s: @Student) {
// s.name.print(); // コンパイルエラー
(*s).name.print();
}
fn main() {
let mut s = Student { name: 'sam', age: 17 };
let snapshot01 = @s;
// sの値を変更して、snapshot01が変更されるかどうかを確認します
s.name = 'tom';
print_student(snapshot01);
print_student(@s);
}
上記のコードでは、可変の Student 構造体変数 s を作成し、@演算子を使用して snapshot01 を取得しました。その後、s の name フィールドを変更し、* 演算子を使用して snapshot01 と最新の snapshot の name フィールドを両方印刷しました。出力結果は、一つは sam、もう一つは tom です。したがって、snapshot は元の変数の変更に影響されません。
また、注意点として、(*s).name.print();
のチェーン呼び出しでは、* 演算子の優先順位が最も低いため、括弧を使用して snapshot の値を優先的に取り出す必要があります。
配列と Snapshot#
12_Cairo1.0 中の Array (配列)では、配列は Copy トレイトを実装していないため、関数の引数として使用すると move 操作が発生します。また、Copy トレイトを実装していない型は、*
演算子を使用してスナップショットから値を取り出すことはできません。それでも、配列の move 操作を回避するために snapshot を使用することはできます。例えば:
use debug::PrintTrait;
use array::ArrayTrait;
fn use_array(arr: @Array<usize>) {
arr.len().print();
let v_z = *arr.at(0);
v_z.print();
}
fn main(){
let mut arr = ArrayTrait::<usize>::new();
arr.append(9);
use_array(@arr);
}
配列全体は*
演算子を使用することはできませんが、要素は使用することができます。ちょうど at が要素の snapshot を返すため、要素を取り出すことができます。
興味深いことに、配列の snapshot は[]
を使用してat
の代わりに使用することができます。次の例を見てみましょう:
use debug::PrintTrait;
use array::ArrayTrait;
fn use_array(arr: @Array<usize>) {
arr.len().print();
// ここで *arr[0] の代わりに *arr.at(0) を使用しています
let v_z = *arr[0];
v_z.print();
}
fn main(){
let mut arr = ArrayTrait::<usize>::new();
arr.append(9);
use_array(@arr);
}
これもコンパイルが通り、配列の snapshot に限定されますが、配列の直接的な読み取りはエラーになります。
また、配列の get メソッドは使用できないようです。次のコードはコンパイルエラーになります:
use debug::PrintTrait;
use array::ArrayTrait;
use option::OptionTrait;
fn use_array(arr: @Array<usize>) {
match arr.get(0) {
Option::Some(v) => {
// ここでは * を使用して関連する値を読み取ることはできません
let tem = *v.unbox();
tem.print();
},
Option::None(_) => {}
}
}
fn main() {
let mut arr = ArrayTrait::<usize>::new();
arr.append(9);
use_array(@arr);
}
参照 (reference)#
関数内でパラメータの値を変更する必要があり、同時に呼び出し元のコンテキストを保持する場合、参照を使用する必要があります。使用方法を見てみましょう:
use core::debug::PrintTrait;
#[derive(Copy, Drop)]
struct Rectangle {
width: felt252,
high: felt252,
}
fn setWidth(ref r: Rectangle, new_width: felt252) {
r.width = new_width;
}
fn main() {
let mut r = Rectangle { width: 100, high: 200 };
setWidth(ref r, 300);
r.width.print();
}
上記のコードでは、setWidth 関数で設定された値が表示されるため、出力される width は setWidth 関数で設定された値です。r 変数を setWidth 関数に渡しても、main 関数で r 変数の値を読み取ることには影響を与えず、setWidth 関数で設定された値が表示されます。
参照は ref キーワードで指定され、関数を定義する際に指定する必要があります。引数を渡す際にも指定する必要があり、参照であることを示します。また、ref で指定された変数は変更可能な変数である必要があります。
Cairo_book では、参照は実際には 2 つの move 操作の省略形であり、変数を呼び出しの関数に move し、所有権を暗黙的に戻す操作です。
配列の ref の使用例#
use debug::PrintTrait;
use array::ArrayTrait;
fn main() {
let mut arr0 = ArrayTrait::new();
fill_array(ref arr0);
// ここでfill_arrayで追加された値が表示されます
arr0.print();
}
fn fill_array(ref arr0: Array<felt252>) {
arr0.append(22);
arr0.append(44);
arr0.append(66);
}