Cairo 核心库实现了一个Array类型。
它与其他编程语言中常见的数组类型相似,但有几个关键区别:
Array类型的尾部,也只能高效地从头部移除。可以使用ArrayTrait::new函数创建数组,也可以使用array!宏:
let empty: Array<u32> = ArrayTrait::new();
let one_to_four = array![1, 2, 3, 4];
可以使用ArrayTrait::append函数向数组中添加元素:
let mut num_array = array![1, 2, 3, 4];
num_array.append(5);
// -> [1, 2, 3, 4, 5]
可以使用ArrayTrait::pop_front函数从数组中移除元素:
let mut one_to_four = array![1, 2, 3, 4];
one_to_four.pop_front();
// -> [2, 3, 4]
Cairo 还引入了Span类型,它就像是Array的“快照”。
每个Array都可以通过ToSpanTrait::span函数转换为Span:
let one_to_four = array![1, 2, 3, 4];
let one_to_four_span = one_to_four.span();
// -> [1, 2, 3, 4]
把数组转换为Span非常高效,因为Span只是一种数据结构,让 Cairo 能够“查看”数组的一段内容,它不会以任何方式修改数组。
通过把数组转换为Span,Cairo 可以执行比直接在Array上更多的操作,比如从 span 的前端移除元素。
let one_to_four = array![1, 2, 3, 4];
let mut one_to_four_span = one_to_four.span();
one_to_four_span.pop_back();
// -> [2, 3, 4]
注意,我们必须用mut关键字把one_to_four_span设为可变的,才能调用pop_back,它会通过缩短 span 所“查看”的数组片段来改变这个 span。
作为一名准魔术师,Elyse 需要练习一些基本功。
她有一摞牌,想摆弄摆弄。
为了省点事,她只用 1 到 10 这些牌。
这摞牌用一个Array表示,牌堆的底部在数组的前端,顶部在数组的后端。
实现函数 insert_top,它返回牌堆的一个副本,并把给定的新牌加到牌堆顶部。
let stack = array![5, 9, 7, 1];
insert_top(stack, 8);
// -> [5, 9, 7, 1, 8]
实现函数 remove_top_card,它返回移除了顶部一张牌后的牌堆副本。
如果给定的牌堆是空的,就原样返回原来的牌堆,不做任何改动。
let stack = array![3, 2, 6, 4, 8];
remove_top_card(stack);
// -> [3, 2, 6, 4]
实现函数 insert_bottom,它返回牌堆的一个副本,并把给定的新牌加到牌堆底部。
let stack = array![5, 9, 7, 1];
insert_bottom(stack, 8);
// -> [8, 5, 9, 7, 1]
实现函数 remove_bottom_card,它返回移除了底部一张牌后的牌堆副本。
如果给定的牌堆是空的,就原样返回原来的牌堆,不做任何改动。
let stack = array![8, 5, 9, 7, 1];
remove_bottom_card(stack);
// -> [5, 9, 7, 1]
实现函数 check_size_of_stack,它检查牌堆的大小是否等于给定的 stack_size。
let stack = array![3, 2, 6, 4, 8];
check_size_of_stack(stack, 4);
// -> false
要理解为什么在 Cairo 中操作数组不像在其他编程语言中那样直截了当,需要记住 Cairo 采用的是不可变内存模型:一旦某个内存单元被写入,就永远无法被覆盖,只能从中读取。
该语言在一定程度上对这一特性做了抽象,让开发者可以声明和使用自己的变量,就好像这些变量是可变的。
尽管这让编写代码变得稍微更具挑战性,尤其是使用Array类型的代码(包括ByteArray,它在底层是一个“字节数组”),但它让编写 可证明的 程序成为可能,这些程序即使在不受信任的机器上也能被放心地执行,因此这一取舍非常值得。