12_Cairo1.0 中の Array(配列)#
この記事で使用されている Cairo コンパイラのバージョン:2.0.0-rc0。Cairo は急速に更新されているため、異なるバージョンの構文が若干異なる場合がありますが、将来的には安定したバージョンに記事の内容を更新する予定です。
配列は非常に一般的なデータ構造であり、通常、同じタイプのデータ要素の集合を表します。配列は、従来の実行可能プログラムやスマートコントラクトの両方で使用されます。
基本的な紹介#
Cairo の配列は、コアライブラリ array
からエクスポートされた配列型であり、いくつかの異なる特徴を持っています。
- Cairo のメモリモデルの特殊性により、メモリスペースは書き込まれると上書きできなくなります。したがって、Cairo 配列の要素は変更できず、読み取りのみ可能です。これは、ほとんどのプログラミング言語とは異なります。
- 配列の末尾に要素を追加できます。
- 配列の先頭から要素を削除することもできます。
配列の作成#
すべての配列は変更可能な変数であるため、mut キーワードが必要です。
fn create_array() -> Array<felt252> {
let mut a = ArrayTrait::new();
a.append(0);
a.append(1);
a
}
配列には任意の型の要素を含めることができます。なぜなら、Array はジェネリック変数を持つからです。配列を作成する際には、型を指定する必要があります。
use array::ArrayTrait;
fn main() {
let mut a = ArrayTrait::new();
// エラー:型注釈が必要です。
}
上記のコードでは、コンパイラは配列 a にどの型のデータを格納するかわからないため、エラーが発生します。次のように型を指定することができます。
use array::ArrayTrait;
fn main() {
let mut a = ArrayTrait::new();
a.append(1);
}
上記のコードでは、felt252 型のデータを配列に追加することで、配列が Array型であることを指定しています。次のようにも指定できます。
use array::ArrayTrait;
fn main() {
let b = ArrayTrait::<usize>::new();
}
上記の 2 つの方法のいずれでも構いません。
配列のサイズ情報の取得#
配列の長さ len()
を取得することができますし、配列が空であるかどうかを判断することもできます is_empty()
。
use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;
fn main() {
let mut a = ArrayTrait::new();
a.append(1);
// 配列が空かどうかを判断する
a.is_empty().print();
// 配列の長さを表示する
a.len().print();
}
要素の追加と削除#
前述のように、Cairo の配列では要素を末尾に追加したり、先頭から要素を削除したりすることができます。関連するコード例を見てみましょう。
use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;
fn main() {
let mut a = ArrayTrait::new();
// 要素を末尾に追加する
a.append(1);
// 最初の要素を削除する
let k = a.pop_front();
k.unwrap().print();
}
要素を末尾に追加するのは簡単ですが、最初の要素を削除するには、pop_front メソッドを使用して削除された要素を Option 型の値として返します。ここでは、unwrap メソッドを使用して Option 型の値を元の型に変換しています。
配列の要素の取得#
配列から要素を取得するには 2 つの方法があります:get 関数と at 関数。
get 関数#
get 関数は比較的安全なオプションであり、Option 型の値を返します。アクセスするインデックスが配列の範囲内にある場合、Some が返されます。インデックスが範囲外の場合は None が返されます。これにより、マッチングパターンを組み合わせて、範囲外の要素を読み取るエラーを回避することができます。
use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;
use box::BoxTrait;
fn main() {
let mut a = ArrayTrait::new();
a.append(1);
let s = get_array(0,a);
s.print();
}
fn get_array(index: usize, arr: Array<felt252>) -> felt252 {
// インデックスはusize型です
match arr.get(index) {
Option::Some(x) => {
// *は元の値をコピーから取得する記号です
// 返されるのはBoxTraitなので、アンボックスするためにunboxを使用する必要があります
*x.unbox()
},
Option::None(_) => {
panic(arr)
}
}
}
上記のコードでは、BoxTrait について説明しますが、公式のコアライブラリの説明ではありません。コピーと参照に関連する内容については、Cairo1.0 の値の渡し方と参照の渡し方を参照してください。
at 関数#
at 関数は、指定したインデックスの要素のスナップショットを直接返します。ここで注意する必要があるのは、これは単一の要素のスナップショットであるため、*
演算子を使用してスナップショットの背後にある値を取得する必要があるということです。また、インデックスが配列の範囲外の場合、panic エラーが発生するため、注意が必要です。
use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;
fn main() {
let mut a = ArrayTrait::new();
a.append(100);
let k = *a.at(0);
k.print();
}
snap 関数#
snap 関数は、配列のスナップショットオブジェクトを取得します。これは読み取り専用のシナリオで非常に便利です。
use core::array::SpanTrait;
use array::ArrayTrait;
fn main() {
let mut a = ArrayTrait::new();
a.append(100);
let s = a.span();
}
配列を関数の引数として使用する#
配列は Copy trait を実装していないため、配列を関数の引数として渡すと、move 操作が発生し、所有権が変更されます。
use array::ArrayTrait;
fn foo(arr: Array<u128>) {}
fn bar(arr: Array<u128>) {}
fn main() {
let mut arr = ArrayTrait::<u128>::new();
foo(arr);
// bar(arr);
}
上記の例では、bar (arr) のコメントを解除するとエラーが発生します。
配列のディープコピー#
名前の通り、ディープコピーはオブジェクトのすべての要素、プロパティ、およびネストされたサブ要素、プロパティを完全にコピーして新しいオブジェクトを作成します。新しいオブジェクトのすべてのデータは元のものと同じですが、メモリ上のアドレスは異なり、データは同じであるが異なるオブジェクトです。
use array::ArrayTrait;
use clone::Clone;
fn foo(arr: Array<u128>) {}
fn bar(arr: Array<u128>) {}
fn main() {
let mut arr = ArrayTrait::<u128>::new();
let mut arr01 = arr.clone();
foo(arr);
bar(arr01);
}
上記のコードは前の例を引き継いでいます。arr01 は arr からディープコピーされた新しい配列であり、コピーには公式のライブラリの Clone trait が必要です。
ディープコピーはリソースを非常に消費することがわかります。clone メソッドはループを使用して配列の要素を 1 つずつコピーします。したがって、この cairo ファイルを実行する際には、ガスを指定する必要があります。
cairo-run --available-gas 200000 $cairo_file
まとめ#
コアライブラリは、配列の長さを簡単に取得したり、要素を追加したり、特定のインデックスの要素を取得したりするための配列型と関連する関数をエクスポートしています。特に、ArrayTrait::get()
関数を使用することは非常に興味深いです。なぜなら、それは Option 型を返すからです。したがって、範囲外のインデックスにアクセスしようとした場合、プログラムが終了するのではなく、None が返されるため、エラー管理機能を実装することができます。さらに、ジェネリック型を配列と組み合わせることで、ポインタ値を手動で管理する古い方法と比較して、配列の使用が容易になります。
配列メンバ関数のまとめ#
trait ArrayTrait<T> {
// 配列を作成する
fn new() -> Array<T>;
// 配列の末尾に要素を追加する
fn append(ref self: Array<T>, value: T);
// 配列の先頭から要素を削除し、削除された要素をOptionの形式で返す
fn pop_front(ref self: Array<T>) -> Option<T> nopanic;
// 特定のインデックスのOption値を取得し、Option型を返す
fn get(self: @Array<T>, index: usize) -> Option<Box<@T>>;
// 特定のインデックスの値を取得する
fn at(self: @Array<T>, index: usize) -> @T;
// 配列の長さを返す
fn len(self: @Array<T>) -> usize;
// 配列が空かどうかを判断する
fn is_empty(self: @Array<T>) -> bool;
// スナップショットを取得する
fn span(self: @Array<T>) -> Span<T>;
}
頑張ってください!💪