Enum
枚举类型类似于结构体,可以同时具备多种数据,但最终使用时只能具备一种数据。
声明
enum IpAddrKind { // 使用 enum 关键字声明枚举,IpAddrKind 是枚举体名称
V4, // 枚举体中可能包含的元素,但最终只能选择 1 个
V6,
}使用:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;使用枚举体生成的变量类型仍然是枚举,可以定义一个函数,接收枚举类型的参数,上述定义的变量可以传入:
fn route(ip_type: IpAddrKind) {}
route(four);
route(six);可以在声明的时候初始化值:
enum IpAddrKind2 {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddrKind2::V4(127, 0, 0, 1);
let loopback = IpAddrKind2::V6(String::from("::1"));枚举在声明时也可以包含多个类型:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u32, u32, u32),
}和结构体一样,也支持定义枚举类型的方法:
impl Message {
fn call(&self) {} // 第一个参数也是 self
}Option<T>
Option 类型在 Rust 中比较经典,为枚举类型,其可能有 2 种值,一种是 None ,也就是空,可以理解为 Python 中的 None 等,另一种是自定义的值。Option 类型的主要目的是为了显式表达一个值可能存在,也可能不存在的情况,避免使用不安全的 null 值,从而减少空指针错误。
let some_number = Some(5);
let some_string = Some("a string");
// absent_number 是一个 Option<i32> 类型,而不是 i32 类型
let absent_number: Option<i32> = None; // 这里允许为 None ,但必须给定类型注解为 Option<i32>使用 Some 表示 Option 中的另一种非空的可能取值,这里的 Some(5) ,即表示另一种的取值类型只可能是 i32 类型(除非显式定义其他类型如 u8 )。
当定义一个 Option 类型为 None 时,需要显式声明类型注解,因为 Rust 中本身不存在 None ,且同时说明当前变量除了可能取值为 None 外,另一种可能的取值为 i32(上述)。
❓ Option 类型定义的变量的类型是 Option 还是具体的值的类型? 如上述的 some_number 的类型是 Option<i32> 还是具体的 i32 ?答案是 Option<i32> ,使用 Option 定义的变量的类型永远都是 Option ,不会是具体值的类型,不是说当值变为 None 时,其类型就从原本的 i32 变为了 None 。
match
match 是 rust 中的模式匹配,他根据传入的枚举体类型的数据,返回相应的值:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
// 在返回值之前也可以进行一些处理
println!("This is a penny!");
1
} // 当定义的 Coin 枚举类型的变量为 Penny 时,返回其值为 1
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}枚举体中也可以嵌套枚举体:
#[derive(Debug)] // 为了后续可以打印
enum UsState {
Alabama,
Alaska,
}
enum Coin2 {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents2(coin: Coin2) -> u8 {
// match 是 rust 中的模式匹配,他根据传入的 Coin 类型的数据,返回相应的值
match coin {
Coin2::Penny => 1,
Coin2::Nickel => 5,
Coin2::Dime => 10,
Coin2::Quarter(state) => {
println!("Now state value is {:?}", state);
25
}
}
}
let coin = Coin2::Quarter(UsState::Alaska); // 🟢 这是阿拉斯加州的 25 美分硬币
let value = value_in_cents2(coin);
println!("Value in cents: {}", value); // 输出:Value in cents: 25枚举体 Coin2 中嵌套了一个枚举体 UsState ,在后面的 match 处理时,传入的 state 就是一个 UsState 枚举体类型,当然,这里并没有使用具体的 state 变量本身,最终返回值为 25 。
后面定义了 coin 变量是 Coin2 枚举体中的 Quarter 数据,则应传入其需要的枚举体,这里传入的是 UsState 中的 Alaska ,最终,在调用 value_in_cents2 函数时,match 将传入的 coin 识别到 Coin2::Quarter(state) 中,最终返回 25 ,因此最终输出 value 的值为 25 。
match 的应用
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
// Option 有两种情况,使用 match 需要全部考虑到,少一种无法通过编译
None => None,
Some(i) => Some(i + 1),
}
}
let x = Some(5);
let six = plus_one(x); // x 是 Option<i32> 类型,经过 match 判定为符合 Some(i) ,返回 Some(i + 1)
let seven = plus_one(six);
let none: Option<i32> = plus_one(None);如上述代码注释种所述,match 的处理不仅限于 Option 类型,对于任何可能取多种值的类型都可以使用 match 判断,如 i32 、u8 等,只是这些类型可能的取值非常多,如 u8 可能取值有 0~255 ,有 256 种,且 match 必须考虑到这些取值的所有情况。不可能将这 255 种情况一一列举,因此有以下处理方法:
let dice_roll: u8 = 9;
match dice_roll {
1 => test_func(),
ot => println!("This was not 1: {}", ot), // 如果最终的变量 ot 没有被使用到,可以使用下划线 _ 表示
}
fn test_func() {}最终使用一个变量兜底。
match 和所有权
观察以下代码示例:
let opt: Option<String> = Some(String::from("hello world"));
match opt {
Some(_) => println!("Some!"), // 由于是下划线表示,没有使用到这个变量,因此所有权不发生移动,后续仍可继续访问 opt
None => println!("None!"),
}
println!("{:?}", opt);
// match opt {
// Some(t) => println!("{:?}", t), // 因为 t 被使用了,所有权发生了移动,后续 opt 不可再使用
// None => println!("None!"),
// }
// println!("{:?}", opt); ❌
// 如果不希望所有权移动,只需使用引用表示
match &opt {
Some(t) => println!("{:?}", t),
None => println!("None!"),
}
println!("{:?}", opt);- 当在
match中使用下划线时,变量的所有权不会发生转移,后续仍可继续使用该变量 - 当使用到了该变量,则所有权发生转移,转移到了
match中的变量中 - 不希望所有权移动,只需使用引用,这样传入
match中的数据是原本变量的引用,内部的变量t也是一个引用,不会发生所有权转移
if let
对比 match ,if let 用于只处理可能的多种取值的变量中的某一种情况,如果使用 match ,即使使用变量兜底,也得将这个语句写出来表示(或者说占位),使用 if let 可以直接处理这种情况。
假设当前只需要处理 Option 类型变量 a 会取值的情况:
let a = Some(5);
match a {
Some(i) => println!("This situation needs to be dealt with"),
_ => println!(
"Even if this case does not need to be handled, it needs to be handled with an '_'"
),
}
if let Some(i) = a {
// 语法说明:只处理是 Some(i) 的情况,处理的对象是变量 a
println!("Matched, value: {}", i);
} else {
println!("None");
}使用 match 需要使用下划线占位,但使用 if let 只需一次处理,且后续可以继续跟 else 增加处理其他的情况。相比于 match 更方便一些。
本节全部代码
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
// four 和 six 都可以作为参数传入到 route() 函数中,说明 four 和 six 都是 IpAddrKind 类型
route(four);
route(six);
// 枚举类型中可以直接给定值
let home = IpAddrKind2::V4(127, 0, 0, 1);
let loopback = IpAddrKind2::V6(String::from("::1"));
// Option<T> 枚举,用于处理可能为 None 的情况
let some_number = Some(5);
let some_string = Some("a string");
// absent_number 是一个 Option<i32> 类型,而不是 i32 类型
let absent_number: Option<i32> = None; // 这里允许为 None ,但必须给定类型注解为 Option<i32>
// match 控制流
let coin = Coin2::Quarter(UsState::Alaska); // 🟢 这是阿拉斯加州的 25 美分硬币
let value = value_in_cents2(coin);
println!("Value in cents: {}", value);
// 使用 Option<T>
let x = Some(5);
let six = plus_one(x);
let seven = plus_one(six);
let none: Option<i32> = plus_one(None);
// match 必须穷尽所有可能的取值情况
let dice_roll: u8 = 9;
match dice_roll {
1 => test_func(),
ot => println!("This was not 1: {}", ot), // 如果最终的变量 ot 没有被使用到,可以使用下划线 _ 表示
}
// match 和所有权
let opt: Option<String> = Some(String::from("hello world"));
match opt {
Some(_) => println!("Some!"), // 由于是下划线表示,没有使用到这个变量,因此所有权不发生移动,后续仍可继续访问 opt
None => println!("None!"),
}
println!("{:?}", opt);
// match opt {
// Some(t) => println!("{:?}", t), // 因为 t 被使用了,所有权发生了移动,后续 opt 不可再使用
// None => println!("None!"),
// }
// println!("{:?}", opt); ❌
// 如果不希望所有权移动,只需使用引用表示
match &opt {
Some(t) => println!("{:?}", t),
None => println!("None!"),
}
println!("{:?}", opt);
// 使用 if let 只处理一种情况
// 如果使用 match ,如果当前变量有 2 种取值,即使只需要其某一种情况的取值,也需要处理另一种情况的取值
let a = Some(5);
match a {
Some(i) => println!("This situation needs to be dealt with"),
_ => println!(
"Even if this case does not need to be handled, it needs to be handled with an '_'"
),
}
if let Some(i) = a {
// 语法说明:只处理是 Some(i) 的情况,处理的对象是变量 a
println!("Matched, value: {}", i);
} else {
println!("None");
}
}
fn route(ip_type: IpAddrKind) {}
enum IpAddrKind {
V4, // 枚举体中可能包含的元素,但最终只能选择 1 个
V6,
}
enum IpAddrKind2 {
V4(u8, u8, u8, u8),
V6(String),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u32, u32, u32),
}
// 枚举也可以实现方法,和结构体类似,第一个参数也是 self
impl Message {
fn call(&self) {}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
// match 是 rust 中的模式匹配,他根据传入的 Coin 类型的数据,返回相应的值
match coin {
Coin::Penny => {
// 在返回值之前也可以进行一些处理
println!("This is a penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
#[derive(Debug)] // 为了后续可以打印
enum UsState {
Alabama,
Alaska,
}
enum Coin2 {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents2(coin: Coin2) -> u8 {
// match 是 rust 中的模式匹配,他根据传入的 Coin 类型的数据,返回相应的值
match coin {
Coin2::Penny => 1,
Coin2::Nickel => 5,
Coin2::Dime => 10,
Coin2::Quarter(state) => {
println!("Now state value is {:?}", state);
25
}
}
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
// Option 有两种情况,使用 match 需要全部考虑到,少一种无法通过编译
// match 表达式的分支,必须穷尽,不仅限于 Option
// 即,match 的变量拥有的所有可能的取值,都应考虑到
None => None,
Some(i) => Some(i + 1),
}
}
fn test_func() {}练习
enum Location {
Point(i32),
Range(i32, i32),
}
fn main() {
// 问题:以下代码能否通过编译?
let l: Location = Location::Range(0, 5);
let n = match l {
Location::Point(_) => -1,
Location::Range(_, a) => a,
Location::Range(0, _) => 0,
_ => -2,
};
println!("{n}");
}答案:
可以,输出 5 。
因为会在第二个分支被捕获,即 Location::Range(_, a) => a ,将 5 赋值给 a ,然后 match 最终返回的是 a(箭头后面指向的 a ),因此 n 的值为 a 的值,也就是 5 ,后续输出 n 即输出 5 。
即使将两个 a 改成 n ,也可以输出 5 ,因为 match 的返回值,即箭头指向的返回值 n 还是会给到 let n 中的 n ,后面输出的 n 就是 let n 中的 n ,输出 5 。