枚举在 Rust 中是非常常用的一个特性,经常会配合 match 使用。

枚举定义

枚举的值可以定义成不同的类型或者不定义,比如 String ,struct ,甚至是其他枚举。

  1. #[derive(Debug)]
  2. enum MyEnum {
  3. A,
  4. B(String),
  5. C { x: u8, y: u8 },
  6. }
  7. fn main() {
  8. let a = MyEnum::A;
  9. println!("{:?}", a);
  10. let b = MyEnum::B(String::from("hello"));
  11. println!("{:?}", b);
  12. let c = MyEnum::C{ x: 1, y: 1 };
  13. println!("{:?}", c);
  14. }

上面的代码打印出来是这样的。
image.png

枚举比较

match

枚举的比较,甚至是想获得枚举里的值内容,就得用到 match 了。

  1. #[derive(Debug)]
  2. enum MyEnum {
  3. A,
  4. B(String),
  5. C { x: u8, y: u8 },
  6. D,
  7. }
  8. fn main() {
  9. println!("{}", test_enum(MyEnum::A));
  10. println!("{}", test_enum(MyEnum::B(String::from("hello"))));
  11. println!("{}", test_enum(MyEnum::C{ x: 1, y: 1 }));
  12. println!("{}", test_enum(MyEnum::D));
  13. }
  14. fn test_enum(b: MyEnum) -> String {
  15. match b {
  16. MyEnum::A => String::from("world"),
  17. MyEnum::B(str) => str,
  18. MyEnum::C { x, y } => (x + y).to_string(),
  19. _ => String::from("any"),
  20. }
  21. }

match 会进行逐个匹配,并且执行匹配命中的函数,在函数中可以获取到枚举里的值,并进行逻辑操作。其中 _ => () 中的 _ 代表通配,也就不需要把 enum 中的所有可能存在的值都写一遍。

match 不仅仅可以用于 enums ,还能用于其他类型,比如下面代码 match 整型和字符串,用 | 可以匹配多个。所以 match 非常强大。

  1. fn test_num(n: u32) -> u32 {
  2. match n {
  3. 1 | 2 => 1, // 1 或者 2 都返回 1
  4. _ => 0, // 兜底返回 0
  5. }
  6. }
  7. fn test_str(n: &str) -> u32 {
  8. match n {
  9. "1" => 1, // 1 或者 2 都返回 1
  10. _ => 0, // 兜底返回 0
  11. }
  12. }

if let

除了可以用 match 进行比较之外,还能用 if let ,在部分场景用 if let 会让代码更简洁一些( 因为不需要写 _ => () 了,两者区别就类似于 JS 中是选 if else 还是 switch case )。所以选择哪种就看场景适合了。

  1. #[derive(Debug)]
  2. enum MyEnum {
  3. A,
  4. B(String),
  5. C { x: u8, y: u8 },
  6. D,
  7. }
  8. fn main() {
  9. println!("{}", test_enum_2(MyEnum::A));
  10. println!("{}", test_enum_2(MyEnum::B(String::from("hello"))));
  11. println!("{}", test_enum_2(MyEnum::C{ x: 1, y: 1 }));
  12. }
  13. fn test_enum_2(b: MyEnum) -> String {
  14. if let MyEnum::A = b {
  15. String::from("world")
  16. } else if let MyEnum::B(str) = b {
  17. // if let 也是可以拿到枚举值的
  18. str
  19. } else {
  20. String::from("any")
  21. }
  22. }

定义方法

Enum 跟 Struct 一样,也可以额外定义方法。

  1. enum MyEnum {
  2. A,
  3. B(String),
  4. C { x: u8, y: u8 },
  5. D,
  6. }
  7. impl MyEnum {
  8. fn test_fn(&self) -> u8 {
  9. // 可以在函数里做一些 match 操作
  10. match &self {
  11. &Self::A => 1,
  12. &Self::B(_str) => 2,
  13. _ => 0,
  14. }
  15. }
  16. }
  17. fn main() {
  18. let a = MyEnum::A;
  19. println!("{}", a.test_fn());
  20. }

在 Enum 实例的方法中,可以用 &Self::A 来替代 &MyEnum::A 的写法。这里有一点要注意的是,实例方法里的入参是引用,因此 match 里的匹配方法里的枚举值,都会是引用,比如 &Self::B 的枚举值就是 &String 了。

Option

Rust 内置了一些枚举,Option 就是其中之一( T 是泛型 )。

  1. enum Option<T> {
  2. None,
  3. Some(T),
  4. }

之所以有这个枚举的存在,是因为在 Rust 中是没有 Null 的,而是通过 Option 来表示某个字段可能为空也可能是非空的场景。

而之所以这么设计的原因,是因为 Rust 觉得提供 Null 会比较容易产生错误( 比如直接使用可能为 Null 的值 ),但是又确实存在这种空与非空的场景,所以就提供了这么个特殊的枚举,正因为它是枚举类型,跟普通类型是不一样的,所以没法直接拿来就用。

比如下面这种用法就报错了,因为 Option 跟 i8 是不同类型,从根本上避免了直接用可能为 Null 的变量场景。

  1. let x: i8 = 5;
  2. let y: Option<i8> = Some(5);
  3. let sum = x + y;

正常的用法大概就类似于下面这种,某个函数返回的值可能是空的,那么就可以定义返回类型为 Option<T> ,调用该函数拿到值的时候,再通过 match 来获取到返回的非空值。

可以留意到,下面的代码没有用 Option::Some ,而是直接用 Some 和 None ,因为 Option 是特殊的,可以直接用 Some 及 None 。

  1. use rand::Rng;
  2. #[derive(Debug)]
  3. enum MyEnum {
  4. A,
  5. B(String),
  6. C { x: u8, y: u8 },
  7. D,
  8. }
  9. fn main() {
  10. // 随机取个 enum 传入函数
  11. let arr = [ MyEnum::A, MyEnum::B(String::from("hello")), MyEnum::D ];
  12. let result = may_be_null(&arr[rand::thread_rng().gen_range(0..arr.len())]);
  13. // 判断是否非空
  14. match result {
  15. Some(n) => {
  16. // n 为非空的值
  17. println!("result is {}", n);
  18. }
  19. None => {
  20. println!("result is Null");
  21. }
  22. }
  23. }
  24. fn may_be_null(b: &MyEnum) -> Option<u32> {
  25. match b {
  26. // 仅 A 和 B 才返回结果,其他返回 None
  27. MyEnum::A => Some(1),
  28. MyEnum::B(_str) => Some(2),
  29. _ => None,
  30. }
  31. }