15_Cairo1.0 中の Snapshot と参照#
この記事で使用されている Cairo コンパイラのバージョン:2.0.0-rc0。Cairo は急速に更新されているため、異なるバージョンの構文にはわずかな違いがあるかもしれませんが、将来的には安定したバージョンに記事の内容を更新する予定です。
14_Cairo1.0 変数の所有権の記事では、Copy トレイトについて言及しました。Copy トレイトを実装したオブジェクトは、関数に渡される際に自動的に変数をコピーし、コピーを関数に渡します。また、変数が Copy トレイトを実装していない場合、関数に渡される際に move 操作が発生します。しかし、プログラミングの過程で、変数の所有権をコンテキスト内に保持したいが、コピー操作は行いたくない場合は、スナップショットと参照を使用することができます。
スナップショット#
スナップショットは、ある時点での変数の値であり、変更不可と読み取り専用の 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 と最新のスナップショットの name フィールドを両方印刷しました。出力結果は、1 つは sam、もう 1 つは tom です。スナップショットは、元の変数の変更によって変更されません。
また、注意点としては、(*s).name.print();
のチェーン呼び出しでは、* 演算子の優先順位が最も低いため、括弧を使用してスナップショットの値を取り出す必要があります。
配列とスナップショット#
12_Cairo1.0 の Array(配列)では、配列は Copy トレイトを実装していないため、関数の引数として使用すると move 操作が発生します。また、Copy トレイトを実装していない型は、*
演算子を使用してスナップショットから値を取得することはできません。それでも、配列の move 操作を回避するためにスナップショットを使用することはできます。例えば:
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 が要素のスナップショットを返すため、要素を取り出すことができます。
興味深い点として、配列のスナップショットは[]
を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);
}
これもコンパイルが通り、配列のスナップショットに限定されますが、配列の直接的な読み取りはエラーになります。
また、配列の 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);
}