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