老前端人学Rust - 第六课(枚举)

176 阅读4分钟

rust有个官方的练习场,在本地没有装rust的情况下,可以用这个网址玩玩。

这节我们来学习枚举。类似typescript,rust的枚举也是使用enum关键字。关于枚举的定义我就不说了,大家应该都知道。他的好处就是可以定义自定义的数据类型,并且有清晰的结构,确保类型安全。

我们就直接进入正题,来学习如何使用枚举。

枚举

如果我们要定义一个IPV4和IPV6的结构,可以使用枚举:

enum IpAddrKind {
    V4,
    V6
}

其中V4和V6叫做变体。

跟结构体不一样的地方在于,结构体是要实例化一整个结构体,而枚举是要单独实例化并使用某个变体。

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

可以在函数中使用这两个实例:

fn route(ip_type: IpAddrKind) {}

route(four);

其实,枚举允许我们直接将关联的数据嵌入到枚举变体内,有点类似ts的枚举赋值。

enum IpAddrKind {
    v4 = [127,0,0,1],
    v6 = "::1"
}
enum IpAddrKind {
    V4(u8, u8, u8, u8),
    V6(String),
}

let home = IpAddrKind::V4(127, 0, 0, 1);
let loopback = IpAddrKind::V6(String::from("::1"));

从上面的例子可以看出来,枚举的变体可以嵌入任意的类型,无论是字符串、数值、结构体,甚至是另一个枚举。

枚举和结构体还有一个相似的地方,就是可以使用impl关键字定义枚举的方法。

impl Message {
    fn call(&self) {        
    }
}

let m = Message::Write(String::from("helo"));
m.call();

Option

可能大家发现了,我们之前介绍rust的数据类型是没有null的。Rust的设计就是如此,设计者觉得空值的问题在于当你想尝试像使用非空值那样去使用空值,就会出现例如空指针异常等问题。但是空值本身所代表的意义又是有用的,所以Rust设计了一个Option枚举,来表示类似的概念。

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

由于Option比较常见且常用,所以内置了预导入模块中,可以不需要显示引用,变体也是一样,可以不加前缀的直接使用Some和None(T是泛型,跟ts里泛型的概念一样)。

因为Some只是一个枚举类型,所以例如相加啊等一系列操作,只能跟Option操作,如:

let x: i8 = 5;
let y: Option<i8> = 6;
    
let sum = x + y; // ❎这样就会报错,Rust无法理解i8和Option<T>相加的行为。
let x: Option<i8> = 5;
let y: Option<i8> = 6;
    
let sum = x + y; // ✅正确

Rust设计Option的好处在我看来,就是为了避免编程人员的心智负担。在正常的数据类型中,永远不会出现空值的情况,所以不需要有判断空的逻辑,也不会出现空指针异常等情况。如果确定需要对空值做处理,就使用Option,这样你先天的就会考虑到这段代码会有空值的情况,需要去做相应的程序处理。

控制运算符match

直接上代码:

enum Coin {
  Penny,
  Nickel,
  Dime,
  Quarter
}
    
fn value_in_cents(coin: Coin) -> u32 {
  match coin {
     Coin::Penny => 1,
     Coin::Nickel => 5,
     Coin::Dime => 10,
     Coin::Quarter => 25,   
  }
}

额,这个运算符在我看来就是switch case,只是多了一些编译器的安全检查,防止你漏掉某些情况,确保了程序的所有可能都得到处理。例如上面的例子,如果只写3个变体判断,就会报错。不过可以使用 _ => ()来表示剩余情况,就像switch case的default。

#[derive(Debug)]
enum Coin {
  Penny,
  Nickel,
  Dime,
  Quarter
}
    
fn value_in_cents(coin: Coin) -> u32 {
  match coin {
     Coin::Penny => 1,
     Coin::Nickel => 5,
     _ => 100
  }
}

fn main() {
    let x = value_in_cents(Coin::Quarter);
    println!("{:?}", x) // 输出100
}

简单控制流if let

如果嫌弃要枚举所有的情况,也可以使用简单控制流if let,用来简写一些只需要特定判断某一种匹配,而忽略其他的匹配的情况,例如:

#[derive(Debug)]
enum Coin {
  Penny,
  Nickel,
  Dime,
  Quarter
}
    
fn value_in_cents(coin: Coin) -> u32 {
  if let Coin::Penny = coin {
      1
  } else {
      100
  }
}

fn main() {
    let x = value_in_cents(Coin::Penny);
    println!("{:?}", x)
}

相当于if else这种的一个变体写法,if let后面跟匹配的类型,=后面是要匹配的变量。

总结

本章我们学会了如何使用枚举来创建自定义类型,也学会了Rust中的空值的表示方法,即使用标准库Option。当枚举包含数据时我们可以使用match关键字或者if let来做匹配。

使用自定义类型可以保证类型安全,编译器会确保函数只会得到他期望的值。