枚举(Enum)
学习目标
- 定义和使用枚举
- 理解枚举变体可以携带数据
- 掌握
Option<T> - 掌握枚举上的方法
核心概念
基本枚举
enum Direction {
North,
South,
East,
West,
}
fn main() {
let dir = Direction::North;
// 用于 match、if let 等
}
携带数据的变体
enum Shape {
Circle(f64), // 半径
Rectangle(f64, f64), // 宽, 高
Triangle { base: f64, height: f64 }, // 命名字段
Point, // 无数据
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle { base, height } => 0.5 * base * height,
Shape::Point => 0.0,
}
}
fn main() {
let shapes = vec![
Shape::Circle(5.0),
Shape::Rectangle(4.0, 6.0),
Shape::Triangle { base: 3.0, height: 8.0 },
];
for s in &shapes {
println!("面积: {:.2}", area(s));
}
}
枚举变体的内存布局
enum Message {
Quit, // 0 字节数据
Move { x: i32, y: i32 }, // 8 字节数据
Write(String), // 24 字节数据(String 大小)
Color(i32, i32, i32), // 12 字节数据
}
// 枚举大小 = 最大变体的大小 + 判别标签
Option<T>(标准库枚举)
Rust 没有 null,用 Option 表示"可能没有值":
// 标准库定义
enum Option<T> {
Some(T), // 有值
None, // 无值
}
fn main() {
let some_number: Option<i32> = Some(42);
let no_number: Option<i32> = None;
// 必须处理 None 的情况
match some_number {
Some(n) => println!("数字是: {}", n),
None => println!("没有数字"),
}
// if let 简写
if let Some(n) = some_number {
println!("数字是: {}", n);
}
}
Option 的常用方法
fn main() {
let x: Option<i32> = Some(5);
let y: Option<i32> = None;
// unwrap:有值返回,None panic
println!("{}", x.unwrap()); // 5
// y.unwrap(); // ❌ panic
// unwrap_or:None 时用默认值
println!("{}", y.unwrap_or(0)); // 0
// map:转换 Some 内的值
let doubled = x.map(|n| n * 2); // Some(10)
// and_then:链式操作
let result = x.and_then(|n| {
if n > 0 { Some(n * 10) } else { None }
});
// is_some / is_none
println!("有值? {}", x.is_some()); // true
println!("空? {}", y.is_none()); // true
}
枚举上的方法
enum TrafficLight {
Red,
Yellow,
Green,
}
impl TrafficLight {
fn duration(&self) -> u32 {
match self {
TrafficLight::Red => 60,
TrafficLight::Yellow => 5,
TrafficLight::Green => 45,
}
}
fn next(&self) -> TrafficLight {
match self {
TrafficLight::Red => TrafficLight::Green,
TrafficLight::Green => TrafficLight::Yellow,
TrafficLight::Yellow => TrafficLight::Red,
}
}
}
fn main() {
let light = TrafficLight::Red;
println!("红灯 {} 秒", light.duration());
let next = light.next();
println!("下一个是绿灯");
}
实践练习
练习 1:IP 地址
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
impl IpAddr {
fn to_string(&self) -> String {
match self {
IpAddr::V4(a, b, c, d) => format!("{}.{}.{}.{}", a, b, c, d),
IpAddr::V6(addr) => addr.clone(),
}
}
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
println!("{}", home.to_string());
println!("{}", loopback.to_string());
}
练习 2:命令枚举
enum Command {
Print(String),
Quit,
Move { x: i32, y: i32 },
Color(u8, u8, u8),
}
fn execute(cmd: &Command) {
match cmd {
Command::Print(msg) => println!("{}", msg),
Command::Quit => println!("退出"),
Command::Move { x, y } => println!("移动到 ({}, {})", x, y),
Command::Color(r, g, b) => println!("颜色: #{:02x}{:02x}{:02x}", r, g, b),
}
}
fn main() {
let commands = vec![
Command::Print(String::from("Hello")),
Command::Move { x: 10, y: 20 },
Command::Color(255, 128, 0),
Command::Quit,
];
for cmd in &commands {
execute(cmd);
}
}
常见错误
1. match 未覆盖所有变体
let x: Option<i32> = Some(5);
match x {
Some(n) => println!("{}", n),
// ❌ 缺少 None 分支
}
2. 滥用 unwrap
let config: Option<String> = None;
// let val = config.unwrap(); // ❌ panic
let val = config.unwrap_or(String::from("default")); // ✅
小结
| 概念 | 说明 |
|---|---|
enum | 定义枚举 |
| 变体 | 枚举的每个可能值 |
| 携带数据 | 变体可以包含不同类型的数据 |
Option<T> | Some(T) 或 None,替代 null |
match | 穷尽匹配枚举变体 |