5分钟速读之Rust权威指南(九)

398 阅读4分钟

枚举

枚举类型,通常也被简称为枚举,它允许我们列举所有可能的值来定义一个类型,对于用过TS的前端同学来说会容易理解一些,不过rust中的枚举类型更强大。

定义枚举

定义一个IP地址的枚举类型,目前有两种被广泛使用的IP地址标准:IPv4和IPv6,两种IP类型被称作变体:

#[derive(Debug)] // 加了Debug trait才能打印每一个枚举值
enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
println!("{:?}", four); // V4
println!("{:?}", six); // V6

使用枚举

枚举值可以用来标识函数参数的类型:

// 标识参数类型
fn route(ip: IpAddrKind) {
    println!("{:?}", ip)
}

route(IpAddrKind::V4); // V4
route(IpAddrKind::V6); // V6

在结构体中使用

定义一个结构体,包含IP类型和IP地址:

struct IpAddr {
    ip: IpAddrKind,
    address: String,
}

// 实现一个IP类型为V4的结构体
let home = IpAddr {
    ip: IpAddrKind::V4,
    address: String::from("127.0.0.1")
};

// 实现一个IP类型为V6的结构体
let loopback = IpAddr {
    ip: IpAddrKind::V6,
    address: String::from("::1")
};

枚举关联类型/值

事实上枚举允许我们直接将其关联的数据嵌入枚举变体内。我们可以使用枚举来更简捷地表达出上述概念,而不用将枚举集成至结构体中:

#[derive(Debug)]
enum IpAddrKind2 {
    V4(String), // 关联一个String类型
    V6(String), // 关联一个String类型
}

// 创建V4变体的枚举,并关联String
let home = IpAddrKind2::V4(String::from("127.0.0.1"));

// 创建V6变体的枚举,并关联String
let loopback = IpAddrKind2::V6(String::from("::1"));

println!("{:?}", home); // V4("127.0.0.1")
println!("{:?}", loopback); // V6("::1")

枚举支持多参数

将V4用多个参数来更加详细的描述地址,其实可以将关联的参数整体理解成元组:

#[derive(Debug)]
enum IpAddrKind3 {
    V4(u8, u8, u8, u8), // 将String改为4个u8
    V6(String),
}

// 使用时也要传入4个u8类型的参数
let home = IpAddrKind3::V4(255, 255, 255, 255);
let loopback = IpAddrKind3::V6(String::from("::1"));

println!("{:?}", home); // V4(255, 255, 255, 255)
println!("{:?}", loopback); // V6("::1")

多类型枚举与结构体

一个枚举中可以包含多个类型的变体:

#[derive(Debug)]
enum Message {
    // 没有关联任何数据
    Quit,
    // 包含一个匿名结构体
    Move { x: i32, y: i32 },
    // 包含一个String
    Write (String),
    // 包含三个i32(一个元祖)
    ChangeColor (u8, u8, u8),
}

// 创建一个Move变体
let mov = Message::Move { x: 100, y: 100 };
println!("{:?}", mov); // Move { x: 100, y: 100 }

枚举和结构体的区别

其实上边的多类型枚举的例子中,每一种变体都可以用单独的结构体实现:

// 结构体
struct Quit; // 空结构体
struct Move { x: i32, y: i32 }
struct Write(String); // 元组结构体
struct ChangeColor(u8, u8, u8); // 元组结构体

两种实现方式之间的差别在于,假如我们使用了不同的结构体,那么每个结构体都会拥有自己的类型,我们无法定义一个能够统一处理这些类型数据的函数,而枚举则不同,因为它是单独的一个类型,只要在函数中标注参数类型为枚举即可:

// 使用枚举来标识参数类型
fn poster(msg: Message) {}

// 使用结构体的话,需要四个不同的函数
fn quit_poster(msg: Quit) {}
fn move_poster(msg: Move) {}
fn write_poster(msg: Write) {}
fn change_color_poster(msg: ChangeColor) {}

为枚举实现方法

枚举也可以像结构体一样实现方法:

impl Message {
    fn call(&self) -> &Message {
        return self;
    }
}
let msg = Message::Move { x: 100, y: 100 };
msg.call(); // Move { x: 100, y: 100 }

Option枚举

rust中没有空值,但却提供了一个拥有类似概念的枚举,我们可以用它来标识一个值无效或缺失。这个枚举就是Option:

enum Option<T> { // 这个枚举中包含一个泛型T,后边章节会介绍泛型
    Some(T),
    None,
}

由于Option非常常用,rust已经做了优化,我们可以在不加Option::前缀的情况下直接使用Some或None:

// rust会自动推断类型
let some_string = Option::Some("some");

// 也可以显式标注类型
let some_string: Option<&str> = Option::Some("some");

// 可以省略Option::
let some_string: Option<&str> = Some("some");

// None必须显式给出类型声明,编译器无法推断出类型
let absent_string: Option<i32> = Option::None;

// 省略Option::
let absent_string: Option<i32> = None;

println!("{:?}", some_string); // Some("some")
println!("{:?}", absent_string); // None

使用Option类型时,其中包含的值不能直接使用:

let x: i32 = 10;
let o:Option<i32> = Some(5);
println!("{}", x + o) // 报错,i32类型不能与Option相加

如果要进行运算,必须要将Option中的T取出来,例如使用if let表达式:

if let Some(v) = o {
  println!("{}", x + v); // 15
}