Rust 枚举类型 (Enums)
你将学到: 作为可辨识联合 (Discriminated Unions) 的 Rust 枚举(真正好用的标签联合)、用于穷尽性模式匹配的
match,以及枚举如何通过编译器强制的安全机制取代 C++ 类层次结构和 C 的标签联合。
- 枚举类型是可辨识联合,即它们是多种可能类型的“求和类型 (Sum Type)”,带有一个用于标识具体变体 (Variant) 的标签。
- 对于 C 开发者:Rust 中的枚举可以携带数据(真正好用的标签联合 —— 编译器会追踪哪个变体是活跃的)。
- 对于 C++ 开发者:Rust 的枚举类似于
std::variant,但支持穷尽性模式匹配,没有std::get异常,也没有std::visit那样繁琐的样板代码。 enum的大小由其可能的最大变体决定。各个变体之间没有关联,并且可以拥有完全不同的类型。enum类型是 Rust 最强大的特性之一 —— 它们可以取代 C++ 中复杂的类继承体系(详见后续案例研究章节)。
fn main() {
enum Numbers {
Zero,
SmallNumber(u8),
BiggerNumber(u32),
EvenBiggerNumber(u64),
}
let a = Numbers::Zero;
let b = Numbers::SmallNumber(42);
let c : Numbers = a; // OK -- a 的类型是 Numbers
let d : Numbers = b; // OK -- b 的类型是 Numbers
}
Rust match 语句
- Rust 的
match相当于“加强版”的 C 语言switch语句:match可用于对简单数据类型、struct、enum进行模式匹配。match语句必须是穷尽的 (Exhaustive),即它们必须复盖给定类型的所有可能情况。_可以用作捕获“所有其他情况”的通配符。match可以产生一个值,但所有分支 (=>) 必须返回相同类型的值。
fn main() {
let x = 42;
// 在这里,_ 复盖了除明确列出以外的所有数字
let is_secret_of_life = match x {
42 => true, // 返回布尔值
_ => false, // 返回布尔值
// 下行无法编译,因为返回类型不是布尔值
// _ => 0
};
println!("{is_secret_of_life}");
}
Rust match 语句的应用
match支持范围匹配、布尔过滤器以及if守卫 (Guard) 语句。
fn main() {
let x = 42;
match x {
// 注意:..=41 表示包含 41 的闭区间范围
0..=41 => println!("小于生命之秘"),
42 => println!("生命之秘"),
_ => println!("大于生命之秘"),
}
let y = 100;
match y {
100 if x == 43 => println!("y 百分之百不是生命之秘"),
100 if x == 42 => println!("y 百分之百是生命之秘"),
_ => (), // 什么都不做
}
}
Rust match 语句与枚举
match和enum经常配合使用:match语句可以将其包含的值“绑定”到一个变量上。如果不对值感兴趣,请使用_。matches!宏可用于测试是否匹配特定的变体。
fn main() {
enum Numbers {
Zero,
SmallNumber(u8),
BiggerNumber(u32),
EvenBiggerNumber(u64),
}
let b = Numbers::SmallNumber(42);
match b {
Numbers::Zero => println!("零"),
Numbers::SmallNumber(value) => println!("小数字 {value}"),
Numbers::BiggerNumber(_) | Numbers::EvenBiggerNumber(_) => println!("较大的数字或更大的数字"),
}
// 针对特定变体进行布尔判断
if matches!(b, Numbers::Zero | Numbers::SmallNumber(_)) {
println!("匹配到了 零 或者 小数字");
}
}
Rust match 语句与解构
match还可以针对解构 (Destructuring) 和切片 (Slices) 执行匹配:
fn main() {
struct Foo {
x: (u32, bool),
y: u32
}
let f = Foo {x: (42, true), y: 100};
match f {
// 将 x 的值捕获到一个叫做 tuple 的变量中
Foo{y: 100, x : tuple} => println!("匹配到了 x: {tuple:?}"),
_ => ()
}
let a = [40, 41, 42];
match a {
// 切片的最后一个元素必须是 42。使用 @ 进行匹配绑定
[rest @ .., 42] => println!("剩余元素: {rest:?}"),
// 切片的第一个元素必须是 42。使用 @ 进行匹配绑定
[42, rest @ ..] => println!("剩余元素: {rest:?}"),
_ => (),
}
}
练习:利用 match 和 enum 实现加减法计算
🟢 入门级
- 编写一个函数,对 64 位无符号整数执行算术运算。
- 第一步:定义一个表示操作的枚举:
#![allow(unused)]
fn main() {
enum Operation {
Add(u64, u64),
Subtract(u64, u64),
}
}
- 第二步:定义一个表示结果的枚举:
#![allow(unused)]
fn main() {
enum CalcResult {
Ok(u64), // 成功结果
Invalid(String), // 无效操作的错误信息
}
}
- 第三步:实现
calculate(op: Operation) -> CalcResult函数- 对于
Add:返回Ok(sum)。 - 对于
Subtract:如果第一个数 >= 第二个数,返回Ok(difference),否则返回Invalid("Underflow")。
- 对于
- 提示:在函数中使用模式匹配:
#![allow(unused)]
fn main() {
match op {
Operation::Add(a, b) => { /* 你的代码 */ },
Operation::Subtract(a, b) => { /* 你的代码 */ },
}
}
参考答案 (点击展开)
enum Operation {
Add(u64, u64),
Subtract(u64, u64),
}
enum CalcResult {
Ok(u64),
Invalid(String),
}
fn calculate(op: Operation) -> CalcResult {
match op {
Operation::Add(a, b) => CalcResult::Ok(a + b),
Operation::Subtract(a, b) => {
if a >= b {
CalcResult::Ok(a - b)
} else {
CalcResult::Invalid("Underflow".to_string())
}
}
}
}
fn main() {
match calculate(Operation::Add(10, 20)) {
CalcResult::Ok(result) => println!("10 + 20 = {result}"),
CalcResult::Invalid(msg) => println!("错误: {msg}"),
}
match calculate(Operation::Subtract(5, 10)) {
CalcResult::Ok(result) => println!("5 - 10 = {result}"),
CalcResult::Invalid(msg) => println!("错误: {msg}"),
}
}
// 输出示例:
// 10 + 20 = 30
// 错误: Underflow
Rust 关联方法 (Associated Methods)
impl块可以为struct、enum等类型定义关联方法。- 方法可以可选地接收
self作为参数。self在概念上类似于 C 语言中作为第一个参数传递的结构体指针,或者是 C++ 中的this。 - 对
self的引用可以是不可变的(默认:&self)、可变的(&mut self),或者获取所有权的(self)。 Self关键字可以作为类型名的缩写。
- 方法可以可选地接收
struct Point {x: u32, y: u32}
impl Point {
fn new(x: u32, y: u32) -> Self {
Point {x, y}
}
fn increment_x(&mut self) {
self.x += 1;
}
}
fn main() {
let mut p = Point::new(10, 20);
p.increment_x();
}
练习:Point 的相加与转换
🟡 中级 —— 此练习旨在加深对方法签名中“移动 (Move)”与“借用 (Borrow)”区别的理解。
- 为
Point结构体实现以下关联方法:add():接收另一个Point,并原地增加当前点的 x 和 y 值(提示:使用&mut self)。transform():消耗现有的Point(提示:使用self),并返回一个新的Point,其 x 和 y 值为原值的平方。
参考答案 (点击展开)
struct Point { x: u32, y: u32 }
impl Point {
fn new(x: u32, y: u32) -> Self {
Point { x, y }
}
fn add(&mut self, other: &Point) {
self.x += other.x;
self.y += other.y;
}
fn transform(self) -> Point {
Point { x: self.x * self.x, y: self.y * self.y }
}
}
fn main() {
let mut p1 = Point::new(2, 3);
let p2 = Point::new(10, 20);
p1.add(&p2);
println!("相加之后: x={}, y={}", p1.x, p1.y); // x=12, y=23
let p3 = p1.transform();
println!("转换之后: x={}, y={}", p3.x, p3.y); // x=144, y=529
// p1 现在无法再被访问了 —— transform() 已经消耗了它的所有权
}