枚举的定义

本章介绍 "枚举"(enumerations),也被称作 enums,是一种自定义数据类型的方式,它由一组固定的命名值成员组成,称为 variants 。枚举对于表示相关值的集合非常有用,其中每个值都是不同的,并且有特定的含义。

Enum Variants and Values

下面是一个枚举的简单例子:

#[derive(Drop)]
enum Direction {
    North,
    East,
    South,
    West,
}

In this example, we've defined an enum called Direction with four variants: North, East, South, and West. The naming convention is to use PascalCase for enum variants. Each variant represents a distinct value of the Direction type. In this particular example, variants don't have any associated value. One variant can be instantiated using this syntax:

#[derive(Drop)]
enum Direction {
    North,
    East,
    South,
    West,
}

fn main() {
    let direction = Direction::North;
}

Now let's imagine that our variants have associated values, that store the exact degree of the direction. We can define a new Direction enum:

#[derive(Drop)]
enum Direction {
    North: u128,
    East: u128,
    South: u128,
    West: u128,
}

fn main() {
    let direction = Direction::North(10);
}

and instantiate it as follows:

#[derive(Drop)]
enum Direction {
    North: u128,
    East: u128,
    South: u128,
    West: u128,
}

fn main() {
    let direction = Direction::North(10);
}

In this code, each variant is associated with a u128 value, representing the direction in degrees. In the next example, we will see that it is also possible to associate different data types with each variant.

It's easy to write code that acts differently depending on the variant of an enum instance, in this example to run specific code according to a direction. You can learn more about it in the Match Control Flow Construct section.

Enums Combined with Custom Types

Enums can also be used to store more interesting custom data associated with each variant. For example:

#[derive(Drop)]
enum Message {
    Quit,
    Echo: felt252,
    Move: (u128, u128),
}

In this example, the Message enum has three variants: Quit, Echo, and Move, all with different types:

  • Quit doesn't have any associated value.
  • Echo is a single felt252.
  • Move is a tuple of two u128 values.

You could even use a Struct or another enum you defined inside one of your enum variants.

Trait Implementations for Enums

在Cairo中,你可以为你的自定义枚举定义trait并实现它们。这允许你定义与枚举相关的方法和行为。下面是一个定义trait并为之前的 Message 枚举实现的例子:

trait Processing {
    fn process(self: Message);
}

impl ProcessingImpl of Processing {
    fn process(self: Message) {
        match self {
            Message::Quit => { println!("quitting") },
            Message::Echo(value) => { println!("echoing {}", value) },
            Message::Move((x, y)) => { println!("moving from {} to {}", x, y) },
        }
    }
}

In this example, we implemented the Processing trait for Message. Here is how it could be used to process a Quit message:


#[derive(Drop)]
enum Message {
    Quit,
    Echo: felt252,
    Move: (u128, u128),
}

trait Processing {
    fn process(self: Message);
}

impl ProcessingImpl of Processing {
    fn process(self: Message) {
        match self {
            Message::Quit => { println!("quitting") },
            Message::Echo(value) => { println!("echoing {}", value) },
            Message::Move((x, y)) => { println!("moving from {} to {}", x, y) },
        }
    }
}
fn main() {
    let msg: Message = Message::Quit;
    msg.process(); // prints "quitting"
}


The Option Enum and Its Advantages

The Option enum is a standard Cairo enum that represents the concept of an optional value. It has two variants: Some: T and None. Some: T indicates that there's a value of type T, while None represents the absence of a value.

enum Option<T> {
    Some: T,
    None,
}

Option 枚举很有用,因为它允许你明确地表示一个值不存在的可能性,使你的代码更具表现力,更容易推理。使用 Option 也可以帮助防止因使用未初始化的或意外的 null 值而引起的错误。

To give you an example, here is a function which returns the index of the first element of an array with a given value, or None if the element is not present.

我们为上述函数演示了两种方法:

  • Recursive approach with find_value_recursive.
  • Iterative approach with find_value_iterative.
fn find_value_recursive(mut arr: Span<felt252>, value: felt252, index: usize) -> Option<usize> {
    match arr.pop_front() {
        Option::Some(index_value) => { if (*index_value == value) {
            return Option::Some(index);
        } },
        Option::None => { return Option::None; },
    };

    find_value_recursive(arr, value, index + 1)
}

fn find_value_iterative(mut arr: Span<felt252>, value: felt252) -> Option<usize> {
    let mut result = Option::None;
    let mut index = 0;

    while let Option::Some(array_value) = arr.pop_front() {
        if (*array_value == value) {
            result = Option::Some(index);
            break;
        };

        index += 1;
    };

    result
}

Enums can be useful in many situations, especially when using the match flow construct that we just used. We will describe it in the next section.

Other enums are used very often, such as the Result enum, allowing to handle errors gracefully. We will explain the Result enum in detail in the "Error Handling" chapter.