枚举

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

枚举成员和值

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

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

在本例中,我们定义了一个名为 Direction 的枚举,它有四个变量:North, East, SouthWest。命名惯例是使用 PascalCase 来命名枚举变量。每个变量代表 Direction 类型的一个不同值。在本示例中,枚举成员没有任何关联值。使用此语法可以实例化一个变量:

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

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

我们可以很轻易的写出根据枚举的成员运行不同的流程的代码,在上面这个例子中,是根据方向来运行特定的代码。你可以在 Match 控制流结构页面上了解更多信息。

枚举与自定义类型相结合

枚举也可以用来存储与每个成员相关的更有趣的数据。比如说:

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

在这个例子中,Message枚举有三个成员:QuitEchoMove,都有不同的类型:

  • Quit 没有任何相关值。
  • Echo 是一个单一的 felt。
  • Move是两个 u128 值组成的的元组。

你甚至可以在你的一个枚举成员中使用一个结构体或另一个你定义的枚举。

枚举的Trait实现

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

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

impl ProcessingImpl of Processing {
    fn process(self: Message) {
        match self {
            Message::Quit => { 'quitting'.print(); },
            Message::Echo(value) => { value.print(); },
            Message::Move((x, y)) => { 'moving'.print(); },
        }
    }
}

在这个例子中,我们为Message实现了Processing trait。下面是如何用它来处理一条退出消息:

use debug::PrintTrait;
#[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 => { 'quitting'.print(); },
            Message::Echo(value) => { value.print(); },
            Message::Move((x, y)) => { 'moving'.print(); },
        }
    }
}
fn main() {
    let msg: Message = Message::Quit;
    msg.process();
}

运行这段代码会打印出 quitting

Option枚举及其优势

Option枚举是一个标准的Cairo枚举,表示一个可选值的概念。它有两个变量:Some: TNone: ()Some:T表示有一个T类型的值,而None表示没有值。

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

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

为了给你一个例子,这里有一个函数,它返回一个给定值的数组中第一个元素的索引,如果该元素不存在则返回None。

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

  • 递归法 find_value_recursive
  • 迭代法 find_value_iterative

注意:将来最好能用循环和无需 gas 的相关代码的简单示例替换此示例。

fn find_value_recursive(arr: @Array<felt252>, value: felt252, index: usize) -> Option<usize> {
    if index >= arr.len() {
        return Option::None;
    }

    if *arr.at(index) == value {
        return Option::Some(index);
    }

    find_value_recursive(arr, value, index + 1)
}

fn find_value_iterative(arr: @Array<felt252>, value: felt252) -> Option<usize> {
    let length = arr.len();
    let mut index = 0;
    let mut found: Option<usize> = Option::None;
    loop {
        if index < length {
            if *arr.at(index) == value {
                found = Option::Some(index);
                break;
            }
        } else {
            break;
        }
        index += 1;
    };
    return found;
}

#[cfg(test)]
mod tests {
    use debug::PrintTrait;
    use super::{find_value_recursive, find_value_iterative};

    #[test]
    #[available_gas(999999)]
    fn test_increase_amount() {
        let mut my_array = ArrayTrait::new();
        my_array.append(3);
        my_array.append(7);
        my_array.append(2);
        my_array.append(5);

        let value_to_find = 7;
        let result = find_value_recursive(@my_array, value_to_find, 0);
        let result_i = find_value_iterative(@my_array, value_to_find);

        match result {
            Option::Some(index) => { if index == 1 {
                'it worked'.print();
            } },
            Option::None => { 'not found'.print(); },
        }
        match result_i {
            Option::Some(index) => { if index == 1 {
                'it worked'.print();
            } },
            Option::None => { 'not found'.print(); },
        }
    }
}

运行这段代码会打印出 it worked

Last change: 2023-09-20, commit: cbb0049