StarknetAstro

StarknetAstro

15_Cairo1.0 中的快照和引用

15_Cairo1.0 中的 Snapshot 和引用#

此文章使用的 Cairo 編譯器版本:2.0.0-rc0。因為 Cairo 正在快速更新,所以不同版本的語法會有些許不同,未來將會將文章內容更新到穩定版本。

14_Cairo1.0 變量所有權一文中我們有提到 Copy trait,實現了 Copy trait 的對象在被傳入函數中的時候,會自動將變量複製,並且將副本傳入到函數裡。另外,如果變量沒有實現 Copy trait,被傳入函數的時候就會發生 move 操作。但是,在編程過程中,我們往往想在上下文中保留變量的所有權,又不想發生複製操作,這時候就可以使用 snapshot 和 reference。

Snapshot#

Snapshot 是變量在某一個時刻的值,具有不可變 和 只讀兩個特性,所以它適合用在傳入參數到函數中,但是不修改參數的情況下使用(類似合約中的 view 函數)。

基本用法#

Cairo 是使用 @ 操作符和 * 操作符來使用 snapshot 的,@用來獲取快照,* 用來讀取快照的值。看:

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 不會隨著原本變量的變化而更改。

還有一個要求是:類型必須實現了 Copy trait 才可以使用 * 操作符讀取 snapshot 的值。

注意⚠️:在 (*s).name.print(); 鏈式調用中,* 操作符的優先級是最低的,所以需要使用括號,優先將 snapshot 的值取出。

數組和 Snapshot#

12_Cairo1.0 中的 Array (數組)裡我們講到,數組沒有實現 Copy trait,所以它作為函數參數時會產生 move 操作。另外,沒有實現 Copy trait 的類型是不可以使用 * 操作符將值從快照中取出的。即使這樣,我們依然可以使用 snapshot 來避免數組的 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 返回的是元素的 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();
}

以上代碼,打印出來的 width 是 setWidth 函數中設置的值。我們可以看到,即使將變量 r 傳入到函數 setWidth 中,依然不影響 main 函數讀取 r 變量的值,而且打印出來的是經過函數 setWidth 設置的值。

引用是使用 ref 關鍵字指定的,在定義函數的時候就需要指定,傳入參數的時候,也需要指定,表明傳入的是引用。另外,使用 ref 標識的變量必須是可變變量。

在 Cairo_book 中寫到:引用其實是兩個 move 操作的簡寫,也就是將傳入的變量 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);
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。