08_Cairo 中の Match 制御モード#
この記事で使用されている Cairo コンパイラのバージョン:2.0.0-rc0。Cairo は現在急速に更新されているため、異なるバージョンの構文が若干異なる場合がありますが、将来的には安定したバージョンに記事の内容を更新する予定です。
Cairo の Match 制御モードは、Rust のそれと 90% 同じです。Cairo はまだ開発中であり、多くの機能が未完成ですが、Rust は比較的安定しているため、この機能を学ぶ際にRust の Match 制御モードを参考にすることができます。
基本的な使い方#
マッチするコードブロックが比較的短い場合は、中括弧を使用せずにコンマで区切ることができます。長い場合は、中括弧を使用する必要があります。
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 を追加して Print 機能を実装する必要があります。
impl UsStatePrintImpl of PrintTrait::<UsState> {
fn print(self: UsState) {
match self {
UsState::Alabama(_) => ('Alabama').print(),
UsState::Alaska(_) => ('Alaska').print(),
}
}
}
Match パターンと Option の組み合わせ#
Match パターンと Option を組み合わせると、非 null チェックを実現することができます。
次のようなロジックを含む関数を実装してみましょう:パラメータが null でない場合は + 1 し、null の場合は何もしない。
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
関数はこの機能を実現し、関数内で null の場合のロジック処理を追加することもできます。また、返り値を使用して null かどうかを判断し、それに応じて null の場合の処理を行うこともできます。
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 の場合の処理がされていないため、コンパイルエラーが発生します。
2 番目のルール:Cairo には現在非常にシンプルなデフォルト効果しかありません。
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();
}