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();
// error: Type annotations needed.
}
上面代碼中,編譯器不知道 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();
}
以上兩種方法都可以。
讀取數組大小信息#
可以讀取數組的長度 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 類型的值轉換為原有的類型。
獲取數組中的元素#
有兩種方法可以獲取數組中的元素:get 函數 和 at 函數。
get 函數#
get 函數是一個相對安全的選項,它返回一個 Option 類型的值。如果訪問的下標沒有超出數組的範圍,那麼就是 Some;如果超出下標,就會返回 None。這樣,我們就可以結合 Match 模式,來分別處理這兩種情況,避免造成:讀取超出下標元素的錯誤。
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 函數將會直接返回對應下標元素的 snapshot,注意這裡只是單個元素的 snapshot,所以我們需要使用*
操作符將這個 snapshot 背後的值取出來。另外,如果下標超出數組的範圍,將會導致 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 函數將會獲得數組的 snapshot 對象,這個在只讀的場景中非常實用。
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 成員方法使用 loop 循環,將數組的元素一個個拷貝出來。所以執行這個 cairo 文件時,需要指定 gas:
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;
// 獲得一個 snapshot
fn span(self: @Array<T>) -> Span<T>;
}
大家加油💪!!