08_Cairo 中的 Match 控制模式#
此文章使用的 Cairo 编译器版本:2.0.0-rc0。因为 Cairo 正在快速更新,所以不同版本的语法会有些许不同,未来将会将文章内容更新到稳定版本。
Cairo 中的 Match 控制模式,90% 与 Rust 的一样。因为 Cairo 还在开发中,许多特性还未完善,Rust 相对稳定,所以我们可以参考Rust 中的 Match 控制模式来对照着学习 Cairo 的这个功能。
基本用法#
如果匹配到的代码块比较简短,可以不用使用大括号,并且以逗号分隔。如果比较长,那么就需要使用大括号。
use debug::PrintTrait;
#[derive(Drop)]
enum Coin {
Penny:(),
Nickel:(),
Dime:(),
Quarter:(),
}
fn value_in_cents(coin: Coin) -> felt252 {
match coin {
Coin::Penny(_) => 1,
Coin::Nickel(_) => 5,
Coin::Dime(_) => 10,
Coin::Quarter(_) => 25,
}
}
fn main() {
let p = Coin::Penny(());
let n = Coin::Nickel(());
value_in_cents(p).print();
value_in_cents(n).print();
}
上述代码中,value_in_cents
的参数类型是 Coin,而 Coin 的子类型的变量也可以作为value_in_cents
的入参。之前说过,Enum 可以理解为一组子类型的集合,所有子类型都可以代表父类型。所以任何一个子类型变量,都可以作为 value_in_cents
函数的参数。
使用大括号的情况:
fn value_in_cents(coin: Coin) -> felt252 {
match coin {
Coin::Penny(_) => {
'Lucky penny!'.print();
1
},
Coin::Nickel(_) => 5,
Coin::Dime(_) => 10,
Coin::Quarter(_) => 25,
}
}
与 Rust 不一样的地方#
(1). Enum 定义时的区别
- 指定 Enum 元素类型的方式不同,Cairo 需要加上冒号
Penny:(u8)
,Rust 不需要冒号Penny(u8)
- 不指定 Enum 元素类型时,Cairo 不可以省略括号
Penny:()
,Rust 可以直接省略
(2). Match 匹配条件中的区别
- 没有指定类型的 Enum 元素,必须使用
(_)
标明,Rust 不需要
(3). 传参时的区别
- 没有指定类型的 Enum 元素,传参时需要传入一个标准类型,比如:
let n = Coin::Nickel(());
Enum 定义时 | Match 匹配条件 | 传参时 | |
---|---|---|---|
Rust | Penny(u8) | Coin::Penny | Coin::Penny() |
Cairo | Penny:(u8) | Coin::Penny(_) | Coin::Penny(()) |
绑定参数#
Enum 是可以添加类型的,Match 的各个匹配项可以将 Enum 绑定的类型作为匹配项的参数。
use debug::PrintTrait;
// #[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama: (),
Alaska: (),
// --snip--
}
enum Coin {
Penny: (),
Nickel: (),
Dime: (),
// 这里添加了一个Enum类型
Quarter: UsState,
}
impl UsStatePrintImpl of PrintTrait<UsState> {
fn print(self: UsState) {
match self {
UsState::Alabama(_) => ('Alabama').print(),
UsState::Alaska(_) => ('Alaska').print(),
}
}
}
fn value_in_cents(coin: Coin) -> felt252 {
match coin {
Coin::Penny(_) => 1,
Coin::Nickel(_) => 5,
Coin::Dime(_) => 10,
Coin::Quarter(state) => {
state.print();
25
}
}
}
fn main() {
let u = Coin::Quarter(UsState::Alabama(()));
value_in_cents(u);
}
上面代码中,UsState::Alabama 就成了 Coin::Quarter (state) 中的参数 state。
与 Rust 不一样的地方#
Cairo 中 Enum 默认是没有实现 PrintTrait 的,所以需要为 UsState 添加一个 impl,用来实现打印功能
impl UsStatePrintImpl of PrintTrait::<UsState> {
fn print(self: UsState) {
match self {
UsState::Alabama(_) => ('Alabama').print(),
UsState::Alaska(_) => ('Alaska').print(),
}
}
}
Match 模式与 Option 搭配使用#
Match 模式与 Option 搭配使用,就能够实现非空判断。
我们尝试实现一个函数,包含逻辑:如果参数不为空,就 + 1,如果为空,就不做任何操作。
use option::OptionTrait;
use debug::PrintTrait;
fn plus_one(x: Option<u8>) -> Option<u8> {
match x {
Option::Some(val) => Option::Some(val + 1_u8),
Option::None(_) => {
// 这里可以增加一些额外操作 ...
Option::None(())
},
}
}
fn main() {
let five: Option<u8> = Option::Some(5_u8);
let six: Option<u8> = plus_one(five);
six.unwrap().print();
let none = plus_one(Option::None(()));
if none.is_none() {
'is none !'.print();
}
}
plus_one
函数就实现了这个功能,可以在函数内部增加一些为空的逻辑处理,也可以在调用处通过返回值来判断是否为空,进而处理为空的情况。
使用 Match 模式的规则#
第一个规则:Match 需要覆盖所有的可能。
use option::OptionTrait;
use debug::PrintTrait;
fn plus_one(x: Option<u8>) -> Option<u8> {
match x {
Option::Some(i) => Option::Some(i + 1_u8),
}
}
fn main() {
let five: Option<u8> = Option::Some(5_u8);
let six = plus_one(five);
let none = plus_one(Option::None(()));
}
以上代码没有处理 None 的情况,所以编译会报错。
第二个规则:Cairo 目前只有很简单的 Default 效果。
use option::OptionTrait;
use debug::PrintTrait;
fn match_default(x: felt252) -> felt252 {
match x {
0 => 'zero',
_ => 'default',
}
}
fn main() {
let r = match_default(0);
r.print();
let r = match_default(1);
r.print();
}