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);
}