StarknetAstro

StarknetAstro

08_Cairo's Match control mode

Match Control Mode in Cairo#

This article uses Cairo compiler version 2.0.0-rc0. Since Cairo is being rapidly updated, there may be some syntax differences 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. Because 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 Enum element type is not specified, Cairo cannot omit the parentheses Penny:(), while Rust can directly omit them.

(2) Differences in Match conditions

  • Enum elements without specified types must be marked with (_) in Cairo, while Rust does not require this.

(3) Differences in passing arguments

  • Enum elements without specified types require passing a standard type as an argument, such as let n = Coin::Nickel(()).
Enum definitionMatch conditionsPassing arguments
RustPenny(u8)Coin::PennyCoin::Penny()
CairoPenny:(u8)Coin::Penny(_)Coin::Penny(())

Binding Parameters#

Enums can have types added to them, and the parameters bound to the Enum can be used as arguments for the matching items in Match.

use debug::PrintTrait;

enum UsState {
    Alabama: (),
    Alaska: (),
}

enum Coin {
    Penny: (),
    Nickel: (),
    Dime: (),
    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#

Enums in Cairo are not implemented with 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 Patterns with Option#

Match patterns can be used with Option to achieve non-null judgment.

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 can add additional logic processing for empty cases inside the function or handle empty cases by checking the return value at the calling site.

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 result in 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.