StarknetAstro

StarknetAstro

Match Control Modes in 08_Cairo

Match Control Mode in Cairo#

The version of the Cairo compiler used in this article is 2.0.0-rc0. Since Cairo is being rapidly updated, there may be some differences in syntax between different versions, and the article content will be updated to the stable version in the future.

The Match control mode in Cairo is 90% similar to Rust. Since Cairo is still under development and many features are not yet complete, we can refer to the Match control mode in Rust to learn about this feature in Cairo.

Basic Usage#

If the matched code block is relatively short, braces can be omitted and separated by commas. If it is longer, braces are needed.

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

In the above code, the parameter type of value_in_cents is Coin, and variables of the subtypes of Coin can also be used as arguments for value_in_cents. As mentioned earlier, an Enum can be understood as a collection of subtypes, and all subtypes can represent the parent type. Therefore, any variable of a subtype can be used as an argument for the value_in_cents function.

Usage with braces:

fn value_in_cents(coin: Coin) -> felt252 {
    match coin {
        Coin::Penny(_) => {
            'Lucky penny!'.print();
            1
        },
        Coin::Nickel(_) => 5,
        Coin::Dime(_) => 10,
        Coin::Quarter(_) => 25,
    }
}

Differences from Rust#

(1) Differences in Enum definition

  • The way to specify the type of Enum elements is different. Cairo requires a colon Penny:(u8), while Rust does not require a colon Penny(u8).
  • When the type of Enum elements is not specified, Cairo cannot omit the parentheses Penny:(), while Rust can omit them directly.

(2) Differences in Match matching conditions

  • If the type of Enum element is not specified, it must be marked with (_), while Rust does not require this.

(3) Differences in passing arguments

  • If the type of Enum element is not specified, a standard type needs to be passed as an argument, for example: let n = Coin::Nickel(());
Enum DefinitionMatch Matching ConditionsPassing Arguments
RustPenny(u8)Coin::PennyCoin::Penny()
CairoPenny:(u8)Coin::Penny(_)Coin::Penny(())

Binding Parameters#

Enums can have types, and the matching items in Match can use the type bound by the Enum as the parameter of the matching item.

use debug::PrintTrait;

// #[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
    Alabama: (),
    Alaska: (),
// --snip--
}

enum Coin {
    Penny: (),
    Nickel: (),
    Dime: (),
    // Here an Enum type is added
    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);
}

In the above code, UsState::Alabama becomes the parameter state in Coin::Quarter(state).

Differences from Rust#

In Cairo, Enums do not implement PrintTrait by default, so an impl needs to be added for UsState to implement the printing function.

impl UsStatePrintImpl of PrintTrait::<UsState> {
    fn print(self: UsState) {
        match self {
            UsState::Alabama(_) => ('Alabama').print(),
            UsState::Alaska(_) => ('Alaska').print(),
        }
    }
}

Match Pattern with Option#

By using Match pattern with Option, non-empty judgment can be achieved.

Let's try to implement a function with the logic: if the parameter is not empty, add 1; if it is empty, do nothing.

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(_) => {
            // Additional operations can be added here ...
            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();
    }
}

The plus_one function implements this functionality, and additional logic processing for empty values can be added inside the function, or the return value can be used at the calling point to determine if it is empty and handle the empty case accordingly.

Rules for Using Match Patterns#

First rule: Match needs to cover all possibilities.

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

The above code does not handle the None case, so it will cause a compilation error.

Second rule: Cairo currently only has a very simple Default effect.

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();
}
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.