结构体与枚举¶
📚 章节概述¶
本章将介绍 Rust 中两种重要的自定义数据类型:结构体(struct)和枚举(enum)。结构体用于组织相关数据,枚举用于定义一组可能的值。我们还将学习模式匹配,这是处理枚举的强大工具。
🎯 学习目标¶
- 掌握结构体的定义和使用
- 学会定义方法和关联函数
- 理解枚举的概念和应用
- 掌握模式匹配的用法
- 学会使用 Option 和 Result 枚举
📖 结构体¶
1.1 定义结构体¶
结构体是组织相关数据的方式:
Rust
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
}
1.2 访问结构体字段¶
Rust
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
println!("Username: {}", user1.username);
println!("Email: {}", user1.email);
}
1.3 结构体更新语法¶
Rust
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
// 使用结构体更新语法,..user1 表示其余字段从 user1 复制
// 注意:String 类型字段会发生所有权转移(move)
let user2 = User {
email: String::from("another@example.com"),
..user1 // 从 user1 中取剩余字段的值
};
println!("User2 email: {}", user2.email);
// println!("{}", user1.username); // 编译错误!user1 的 username 已被移动
}
1.4 元组结构体¶
Rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("Black: ({}, {}, {})", black.0, black.1, black.2);
}
1.5 类单元结构体¶
🔧 方法¶
2.1 定义方法¶
方法与函数类似,但在结构体的上下文中定义:
Rust
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle { // impl 为类型实现方法
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
2.2 关联函数¶
不使用 self 的函数称为关联函数:
Rust
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
println!("Square: {}x{}", sq.width, sq.height);
}
2.3 多个 impl 块¶
Rust
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
// 判断当前矩形是否能完全包含另一个矩形
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}
🔢 枚举¶
3.1 定义枚举¶
枚举允许你定义一个类型,该类型的值可以是几个可能的变体之一:
Rust
enum IpAddr {
V4(String),
V6(String),
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
3.2 枚举变体¶
Rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let m = Message::Write(String::from("hello"));
}
3.3 枚举上的方法¶
Rust
impl Message {
fn call(&self) {
// 使用 match 对枚举的每个变体进行模式匹配
// 每个分支可以解构出变体中携带的数据
match self { // match 模式匹配,穷举所有可能的值
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y), // 解构命名字段
Message::Write(s) => println!("Write: {}", s), // 解构元组字段
Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),
}
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
🎯 Option 枚举¶
4.1 Option 定义¶
Option 是 Rust 中处理可能为空的值的枚举:
4.2 使用 Option¶
Rust
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
println!("some_number: {:?}", some_number);
println!("some_string: {:?}", some_string);
println!("absent_number: {:?}", absent_number);
}
4.3 处理 Option¶
Rust
fn main() {
let some_number = Some(5);
match some_number {
Some(value) => println!("Got value: {}", value),
None => println!("No value"),
}
// 使用 if let
if let Some(value) = some_number {
println!("Got value: {}", value);
}
}
🎭 模式匹配¶
5.1 match 表达式¶
Rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {
let coin = Coin::Penny;
println!("Value: {} cents", value_in_cents(coin));
}
5.2 绑定值¶
Rust
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
// 匹配 Quarter 变体时,将内部的 UsState 值绑定到 state 变量
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
fn main() {
let coin = Coin::Quarter(UsState::Alaska);
println!("Value: {} cents", value_in_cents(coin));
}
5.3 匹配 Option¶
Rust
// 对 Option 进行模式匹配,实现安全的值转换
// None 保持为 None,Some(i) 则取出值加1后重新包装
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("five: {:?}", five);
println!("six: {:?}", six);
println!("none: {:?}", none);
}
5.4 匹配所有可能性¶
Rust
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other), // other 是通配模式,捕获所有未匹配的值并绑定到变量
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
}
5.5 if let 简化匹配¶
Rust
fn main() {
let some_value = Some(0u8);
match some_value {
Some(3) => println!("three"),
_ => println!("not three"),
}
// 使用 if let
if let Some(3) = some_value {
println!("three");
} else {
println!("not three");
}
}
📝 练习题¶
练习 1:结构体定义¶
Rust
// TODO: 定义一个 Book 结构体,包含:
// - title: String
// - author: String
// - pages: u32
// - price: f64
// TODO: 为 Book 实现一个方法 total_cost(quantity: u32) -> f64
fn main() {
// TODO: 创建一个 Book 实例并测试
}
练习 2:枚举定义¶
Rust
// TODO: 定义一个 TrafficLight 枚举,包含:
// - Red
// - Yellow
// - Green
// TODO: 为 TrafficLight 实现一个方法 duration() -> u32
// Red: 30秒, Yellow: 5秒, Green: 45秒
fn main() {
// TODO: 测试 TrafficLight
}
练习 3:模式匹配¶
Rust
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Square(f64),
}
// TODO: 实现 area 函数,计算不同形状的面积
// 提示:Circle 面积 = π * r²,Rectangle = w * h,Square = s²
fn area(shape: Shape) -> f64 {
// 使用 match 表达式对 Shape 的每个变体进行匹配
todo!()
}
fn main() {
let circle = Shape::Circle(5.0);
let rectangle = Shape::Rectangle(4.0, 6.0);
let square = Shape::Square(3.0);
println!("Circle area: {}", area(circle));
println!("Rectangle area: {}", area(rectangle));
println!("Square area: {}", area(square));
}
练习 4:Option 处理¶
Rust
// TODO: 编写一个函数,将字符串转换为整数
// 如果转换成功,返回 Some(i32)
// 如果转换失败,返回 None
fn parse_int(s: &str) -> Option<i32> {
// TODO: 实现
todo!()
}
fn main() {
let valid = "123";
let invalid = "abc";
println!("Parse '{}': {:?}", valid, parse_int(valid));
println!("Parse '{}': {:?}", invalid, parse_int(invalid));
}
💡 最佳实践¶
1. 使用结构体组织相关数据¶
Rust
// 好
struct User {
name: String,
email: String,
age: u32,
}
// 避免
fn create_user(name: String, email: String, age: u32) {
// 使用多个参数
}
2. 使用枚举表示有限的选项¶
Rust
// 好
enum Status {
Active,
Inactive,
Pending,
}
// 避免
struct Status {
is_active: bool,
is_pending: bool,
}
3. 使用 Option 而不是空值¶
Rust
// 好
fn find_user(id: u32) -> Option<User> {
// 返回 Option<User>
}
// 避免
fn find_user(id: u32) -> User {
// 返回空 User 或使用 panic!
}
4. 使用模式匹配处理枚举¶
Rust
// 好
fn process_status(status: Status) {
match status {
Status::Active => println!("User is active"),
Status::Inactive => println!("User is inactive"),
Status::Pending => println!("User is pending"),
}
}
// 避免
fn process_status(status: Status) {
if status == Status::Active {
println!("User is active");
}
// 需要多个 if 语句
}
⚠️ 常见错误¶
1. 忘记实现 trait¶
Rust
// 错误
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("{:?}", rect); // 编译错误!Rectangle 没有实现 Debug
}
// 正确
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("{:?}", rect);
}
2. match 不完整¶
Rust
// 错误
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
// 编译错误!没有处理所有情况
}
}
// 正确
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
3. 忽略 None¶
Rust
// 错误
fn divide(a: f64, b: f64) -> f64 {
a / b // 如果 b 为 0,会返回 inf
}
// 正确
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
📚 扩展阅读¶
🎯 本章小结¶
本章介绍了 Rust 中的结构体和枚举:
- ✅ 掌握结构体的定义和使用
- ✅ 学会定义方法和关联函数
- ✅ 理解枚举的概念和应用
- ✅ 掌握模式匹配的用法
- ✅ 学会使用 Option 枚举处理可能为空的值
下一章: 我们将学习 Rust 的错误处理机制,包括 Result 类型、Option 类型和错误传播。