StarknetAstro

StarknetAstro

15_Cairo1.0 中的Snapshot和引用

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);
}
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。