Rust 基础-结构体和枚举(第三章)

1. 结构体

1. 语法

一个结构体由几部分组成:

  • 通过关键字 struct 定义
  • 清晰明确的结构体名称
  • 有名字和类型的结构体字段(field)
1
2
3
4
5
6
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}

通过为每个字段指定具体值来创建这个结构体的实例:

  • 初始化实例时,每个字段都需要进行初始化
  • 初始化时的字段顺序不需要和结构体定义时的顺序一致
  • 当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化
  • 根据已有的结构体实例,创建新实例时,可以在尾部使用 .. 简化赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

// 缩略同名字段
fn build_user(email: String, username: String) -> User {
User {
email, // 简写 email: email
username, // 简写 username: username
active: true,
sign_in_count: 1,
}
}

// 根据已有的结构体实例创建,必须在尾部使用简写
let user2 = User {
email: String::from("another@example.com"),
..user1
};

// username 所有权被转移给了 user2,导致了 user1 无法再被使用
// 但是并不代表 user1 内部的其它字段不能被继续使用
println!("{}", user1.active);
// 下面这行会报错
println!("{:?}", user1);
}

通过 . 操作符,访问或修改结构体实例内部的字段值:

  • 必须将结构体实例声明为可变的,才能修改其中的字段
  • 不支持将结构体某个字段标记为可变
1
user1.email = String::from("anotheremail@example.com");

2. 特殊结构体

1. 元组结构体(tuple struct)

定义与元组类似的结构体,结构体有名称,但是字段没有,其称为元组结构体。

1
2
3
4
5
6
7
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}

2. 单元结构体(Unit-like Struct)

可以一个没有任何字段的结构体,其被称为单元结构体。主要是为了在某个类型上实现 trait 但不需要在类型中存储数据的时候使用。

1
2
3
4
5
struct AlwaysEqual;

fn main() {
let subject = AlwaysEqual;
}

3. 结构体数据的所有权

如上 User 例子,我们希望某个结构体拥有它所有的数据,而不是从其它地方借用数据,于是使用了自身拥有所有权的 String 类型而不是基于引用的 &str 字符串切片类型。

当需要使结构体存储被其他对象拥有的数据的引用,需要使用生命周期(lifetime),来确保结构体的作用范围要比它所借用的数据的作用范围要小。

2. 枚举

枚举(enum 或 enumeration)允许通过列举可能的成员来定义一个枚举类型,每个元素成为成员(variants)。

1
2
3
4
5
// IPv4 和 IPv6 的枚举
enum IpAddrKind {
V4,
V6,
}

可以将数据信息关联到枚举成员上,特别的,与其他语言不同的是,同一个枚举类型下的不同成员还能持有不同的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 扑克花色枚举
enum PokerCard {
Clubs(u8),
Spades(u8),
Diamonds(u8),
Hearts(u8),
}

// 将 v4 和 v6 使用不同方式存储
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}

// 使用结构体来定义枚举成员数据类型
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}

struct Ipv4Addr {
// --snip--
}

struct Ipv6Addr {
// --snip--
}

1. Option 枚举用于处理空值

Rust 中没有 null 关键字,它使用 Option 枚举,来处理空值。它被包含在了 prelude 中,不需要将其显式引入作用域就可以使用。

1
2
3
4
enum Option<T> {// T 是泛型参数
Some(T),
None,
}

3. 模式匹配

模式匹配最常用的就是 matchif let

1. match

match 类似其他语言中的 switch ,允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行对应的代码。

一般形式如下:

1
2
3
4
5
6
7
8
9
match target {
模式1 => 表达式1,
模式2 => {
语句1;
语句2;
表达式2
},
_ => 表达式3
}

有几个注意事项:

  • 匹配必须要穷举出所有可能,可以使用 _ 或一个变量来代表未列出的其他可能性
  • 每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
  • X|Y,类似逻辑运算符或,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Direction {
East,
West,
North,
South,
}

fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::South => {
println!("South or North");
},
_ => println!("West"),
};
}

2. if let

用来处理只匹配一个模式的值而忽略其他模式的情况。

1
2
3
if let Some(3) = v {
println!("three");
}