第六章:结构体与枚举 —— Rust 的自定义类型
第六章:结构体与枚举 —— Rust 的自定义类型
本章目标
- 掌握结构体(Struct)的定义与使用,对比 TypeScript 的 interface/class
- 理解方法(Method)与关联函数(Associated Function),掌握
impl块- 深入理解枚举(Enum),对比 TypeScript 的 union type 和 enum
- 熟练使用
Option<T>,对比 TypeScript 的T | null | undefined- 掌握模式匹配(Pattern Matching)和
match表达式- 学会常用的枚举模式和惯用法
- 通过练习题巩固所学
预计学习时间:90 - 120 分钟
6.1 结构体(Struct)—— 自定义数据类型
6.1.1 基本结构体定义
结构体让你把相关的数据组合在一起,就像 TypeScript 的 interface 或 class:
// TypeScript
interface User {
name: string;
email: string;
age: number;
active: boolean;
}
const user: User = {
name: "动动",
email: "dong@example.com",
age: 28,
active: true,
};
// Rust - 结构体定义
struct User {
name: String, // 注意:用逗号分隔,不是分号
email: String,
age: u32,
active: bool,
}
fn main() {
// 创建结构体实例
let user = User {
name: String::from("动动"),
email: String::from("dong@example.com"),
age: 28,
active: true,
};
// 访问字段
println!("用户名: {}", user.name);
println!("年龄: {}", user.age);
}
6.1.2 对比 TypeScript 的 interface 和 class
┌──────────────────────────────────────────────────────────────┐
│ TypeScript vs Rust 的类型定义 │
├───────────────────────────┬──────────────────────────────────┤
│ TypeScript │ Rust │
├───────────────────────────┼──────────────────────────────────┤
│ interface User { │ struct User { │
│ name: string; │ name: String, │
│ age: number; │ age: u32, │
│ } │ } │
├───────────────────────────┼──────────────────────────────────┤
│ 只是类型约束 │ 真正的数据类型 │
│ 编译后消失 │ 编译后存在于内存中 │
│ 没有运行时表现 │ 有明确的内存布局 │
├───────────────────────────┼──────────────────────────────────┤
│ class User { │ struct User { ... } │
│ constructor( │ impl User { │
│ public name: string │ fn new(name: String) -> Self │
│ ) {} │ { ... } │
│ greet() { ... } │ fn greet(&self) { ... } │
│ } │ } │
├───────────────────────────┼──────────────────────────────────┤
│ 数据和方法写在一起 │ 数据(struct)和行为(impl)分开 │
│ 支持继承 │ 不支持继承,用组合和 trait │
└───────────────────────────┴──────────────────────────────────┘
6.1.3 可变性与字段修改
fn main() {
// 注意:Rust 不允许单个字段声明为 mut
// 整个结构体要么全部可变,要么全部不可变
let mut user = User {
name: String::from("动动"),
email: String::from("dong@example.com"),
age: 28,
active: true,
};
// 整个实例是 mut 的,所以可以修改任何字段
user.email = String::from("new_dong@example.com");
user.age = 29;
// 对比 TypeScript:
// TS 中可以用 readonly 标记单个字段
// interface User {
// readonly name: string; // 单独标记不可变
// email: string; // 可变
// }
}
6.1.4 字段初始化简写
fn build_user(name: String, email: String) -> User {
User {
name, // 简写:等同于 name: name
email, // 简写:等同于 email: email
age: 0,
active: true,
}
// 和 JS/TS 的对象简写一样!
// const obj = { name, email }; // JS 中的等价写法
}
6.1.5 结构体更新语法(Spread)
fn main() {
let user1 = User {
name: String::from("动动"),
email: String::from("dong@example.com"),
age: 28,
active: true,
};
// 用 .. 语法从另一个实例中取剩余字段(类似 JS 的 spread)
let user2 = User {
email: String::from("yang@example.com"),
name: String::from("小羊"),
..user1 // 从 user1 取 age 和 active
};
// ⚠️ 注意所有权!
// user1.age 和 user1.active 是 Copy 类型,没问题
// 但如果 user1 的 String 字段被 .. 使用了,user1 就被部分移动了
println!("user2: {} ({})", user2.name, user2.age);
// 在这个例子中 user1 的 name 和 email 没被 .. 使用
// (我们手动指定了),所以 user1 的 String 字段没被移动
// 但 user1 整体仍被部分移动(因为 .. 语法的语义)
}
对比 TypeScript 的 spread:
// TypeScript
const user1 = { name: "动动", email: "dong@example.com", age: 28, active: true };
const user2 = { ...user1, email: "yang@example.com", name: "小羊" };
// user1 不受影响(JS 做了浅拷贝)
console.log(user1.name); // ✅ "动动"
6.1.6 元组结构体(Tuple Struct)
// 当你需要给元组一个名字,但不需要命名每个字段时
struct Color(u8, u8, u8); // RGB 颜色
struct Point(f64, f64); // 2D 坐标
struct Meters(f64); // 新类型模式(Newtype Pattern)
fn main() {
let red = Color(255, 0, 0);
let origin = Point(0.0, 0.0);
let distance = Meters(100.0);
// 用索引访问
println!("R: {}, G: {}, B: {}", red.0, red.1, red.2);
println!("x: {}, y: {}", origin.0, origin.1);
// 新类型模式的好处:类型安全
// Meters(100.0) 和 f64 是不同类型,不能混用
// let wrong: f64 = distance; // ❌ 类型不匹配
let value: f64 = distance.0; // ✅ 显式取出内部值
}
6.1.7 单元结构体(Unit Struct)
// 没有任何字段的结构体
struct Marker;
struct AlwaysEqual;
fn main() {
let _m = Marker;
let _a = AlwaysEqual;
// 主要用于实现 trait,不需要存储数据
// 类似于 TypeScript 的标记类型
}
6.1.8 结构体的打印与调试
// 要打印结构体,需要派生 Debug trait
#[derive(Debug)]
struct User {
name: String,
email: String,
age: u32,
}
fn main() {
let user = User {
name: String::from("动动"),
email: String::from("dong@example.com"),
age: 28,
};
// {:?} 使用 Debug 格式
println!("{:?}", user);
// 输出:User { name: "动动", email: "dong@example.com", age: 28 }
// {:#?} 使用格式化的 Debug 格式(更易读)
println!("{:#?}", user);
// 输出:
// User {
// name: "动动",
// email: "dong@example.com",
// age: 28,
// }
// 也可以用 dbg! 宏(输出到 stderr,包含文件名和行号)
dbg!(&user);
}
6.2 方法与关联函数(impl 块)
6.2.1 定义方法
方法类似于 TypeScript class 中的方法,但在 Rust 中通过 impl 块定义,数据和行为是分离的:
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
// impl 块:为 Rectangle 定义方法
impl Rectangle {
// 方法:第一个参数是 self(或 &self, &mut self)
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn is_square(&self) -> bool {
(self.width - self.height).abs() < f64::EPSILON
}
}
fn main() {
let rect = Rectangle { width: 10.0, height: 5.0 };
// 调用方法:用 . 语法
println!("面积: {}", rect.area());
println!("周长: {}", rect.perimeter());
println!("是正方形: {}", rect.is_square());
}
6.2.2 self 的三种形式
impl Rectangle {
// &self —— 不可变借用(只读)
// 最常用,只需要读取数据
fn area(&self) -> f64 {
self.width * self.height
}
// &mut self —— 可变借用(可修改)
// 需要修改自身数据
fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
// self —— 获取所有权(消费自身)
// 调用后原实例不再可用
// 通常用于转换操作
fn into_square(self) -> Rectangle {
let side = (self.width + self.height) / 2.0;
Rectangle { width: side, height: side }
}
}
fn main() {
let mut rect = Rectangle { width: 10.0, height: 5.0 };
println!("面积: {}", rect.area()); // &self:rect 仍可用
rect.scale(2.0); // &mut self:rect 被修改
println!("缩放后: {:?}", rect); // rect 仍可用
let square = rect.into_square(); // self:rect 被消费
// println!("{:?}", rect); // ❌ rect 已被移动
println!("正方形: {:?}", square); // ✅ square 可用
}
对比 TypeScript:
class Rectangle {
constructor(public width: number, public height: number) {}
// TS 中所有方法都隐式地有 this(类似 &self 或 &mut self)
area(): number {
return this.width * this.height;
}
scale(factor: number): void {
// TS 中可以自由修改 this,没有 &self / &mut self 的区分
this.width *= factor;
this.height *= factor;
}
// TS 中没有"消费自身"的概念
}
6.2.3 关联函数(Associated Functions)—— 没有 self 的函数
impl Rectangle {
// 关联函数:没有 self 参数
// 类似于 TypeScript 的 static 方法
fn new(width: f64, height: f64) -> Rectangle {
Rectangle { width, height }
}
fn square(size: f64) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
fn from_diagonal(diagonal: f64, ratio: f64) -> Rectangle {
// 根据对角线和宽高比计算
let height = (diagonal * diagonal / (1.0 + ratio * ratio)).sqrt();
let width = height * ratio;
Rectangle { width, height }
}
}
fn main() {
// 用 :: 调用关联函数(不是 .)
let rect1 = Rectangle::new(10.0, 5.0);
let rect2 = Rectangle::square(7.0);
let rect3 = Rectangle::from_diagonal(10.0, 2.0);
println!("{:?}", rect1);
println!("{:?}", rect2);
println!("{:?}", rect3);
}
对比 TypeScript:
class Rectangle {
// static 方法 ≈ Rust 的关联函数
static new(width: number, height: number): Rectangle {
return new Rectangle(width, height);
}
static square(size: number): Rectangle {
return new Rectangle(size, size);
}
}
// 调用方式类似
const rect = Rectangle.new(10, 5);
💡 约定:在 Rust 中,通常用
new作为构造函数的名字,但这只是约定,不是语言要求。Rust 没有new关键字。
6.2.4 多个 impl 块
struct Player {
name: String,
health: i32,
attack: i32,
}
// 可以有多个 impl 块(对组织代码有帮助)
impl Player {
fn new(name: String) -> Player {
Player {
name,
health: 100,
attack: 10,
}
}
}
// 战斗相关的方法
impl Player {
fn take_damage(&mut self, amount: i32) {
self.health -= amount;
if self.health < 0 {
self.health = 0;
}
println!("{} 受到 {} 点伤害,剩余生命值: {}",
self.name, amount, self.health);
}
fn is_alive(&self) -> bool {
self.health > 0
}
fn attack_target(&self, target: &mut Player) {
println!("{} 攻击了 {}!", self.name, target.name);
target.take_damage(self.attack);
}
}
// 显示相关的方法
impl Player {
fn status(&self) -> String {
format!("[{}] HP: {}/100 ATK: {}",
self.name, self.health, self.attack)
}
}
fn main() {
let mut player1 = Player::new(String::from("动动"));
let mut player2 = Player::new(String::from("小羊"));
println!("{}", player1.status());
println!("{}", player2.status());
// 注意:不能同时可变借用 player1 和通过 player1 调用方法
// 所以这里需要分开处理
let damage = player1.attack;
player2.take_damage(damage);
println!("{}", player2.status());
}
6.2.5 自动引用和解引用
// Rust 有一个很方便的特性:自动引用和解引用
// 当你用 . 调用方法时,Rust 会自动添加 &、&mut 或 *
impl Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle::new(10.0, 5.0);
// 这些写法是等价的:
let a1 = rect.area(); // Rust 自动添加 &
let a2 = (&rect).area(); // 手动写 &(不需要)
// 这和 C/C++ 不同,C 中你需要区分 . 和 ->
// Rust 统一用 .,由编译器自动处理
}
6.3 枚举(Enum)—— 比你想象的强大得多
6.3.1 基本枚举
// Rust 的枚举
enum Direction {
Up,
Down,
Left,
Right,
}
fn main() {
let dir = Direction::Up;
// 用 match 处理枚举
match dir {
Direction::Up => println!("向上"),
Direction::Down => println!("向下"),
Direction::Left => println!("向左"),
Direction::Right => println!("向右"),
}
}
对比 TypeScript:
// TypeScript 的 enum
enum Direction {
Up,
Down,
Left,
Right,
}
const dir = Direction.Up;
// TS 的 enum 本质上是数字或字符串映射
// Rust 的 enum 更强大,每个变体可以携带不同类型的数据
6.3.2 携带数据的枚举 —— Rust 的杀手锏
这是 Rust 枚举最强大的特性 —— 每个变体可以携带不同类型和数量的数据:
// 每个变体可以携带不同的数据
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 命名字段(类似结构体)
Write(String), // 单个 String
ChangeColor(u8, u8, u8), // 三个 u8(RGB)
}
fn main() {
let msg1 = Message::Quit;
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::Write(String::from("hello"));
let msg4 = Message::ChangeColor(255, 0, 128);
process_message(msg2);
process_message(msg3);
}
fn process_message(msg: Message) {
match msg {
Message::Quit => {
println!("退出");
}
Message::Move { x, y } => {
println!("移动到 ({}, {})", x, y);
}
Message::Write(text) => {
println!("写入: {}", text);
}
Message::ChangeColor(r, g, b) => {
println!("颜色: rgb({}, {}, {})", r, g, b);
}
}
}
6.3.3 对比 TypeScript 的 Union Type
TypeScript 中最接近 Rust 枚举的是可辨识联合类型(Discriminated Union):
// TypeScript:可辨识联合类型
type Message =
| { kind: "quit" }
| { kind: "move"; x: number; y: number }
| { kind: "write"; text: string }
| { kind: "changeColor"; r: number; g: number; b: number };
function processMessage(msg: Message) {
switch (msg.kind) {
case "quit":
console.log("退出");
break;
case "move":
console.log(`移动到 (${msg.x}, ${msg.y})`);
break;
case "write":
console.log(`写入: ${msg.text}`);
break;
case "changeColor":
console.log(`颜色: rgb(${msg.r}, ${msg.g}, ${msg.b})`);
break;
}
}
┌──────────────────────────────────────────────────────────────┐
│ Rust Enum vs TypeScript Discriminated Union │
├──────────────────────────┬───────────────────────────────────┤
│ Rust │ TypeScript │
├──────────────────────────┼───────────────────────────────────┤
│ enum 定义变体 │ type 联合多个类型 │
│ 编译器保证穷尽匹配 │ switch 可能遗漏(可配置检查) │
│ 零成本:编译后是高效标签 │ 运行时有 kind 字段的开销 │
│ match 表达式是值 │ switch 是语句(需要 break) │
│ 可以有方法(impl) │ 需要写独立的函数 │
│ 编译时确定大小 │ 运行时动态 │
└──────────────────────────┴───────────────────────────────────┘
6.3.4 为枚举实现方法
enum Shape {
Circle(f64), // 半径
Rectangle { width: f64, height: f64 },
Triangle(f64, f64, f64), // 三边长
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(radius) => {
std::f64::consts::PI * radius * radius
}
Shape::Rectangle { width, height } => {
width * height
}
Shape::Triangle(a, b, c) => {
// 海伦公式
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
}
}
fn describe(&self) -> String {
match self {
Shape::Circle(r) => format!("圆形(半径: {})", r),
Shape::Rectangle { width, height } =>
format!("矩形({}×{})", width, height),
Shape::Triangle(a, b, c) =>
format!("三角形(边: {}, {}, {})", a, b, c),
}
}
}
fn main() {
let shapes: Vec<Shape> = vec![
Shape::Circle(5.0),
Shape::Rectangle { width: 10.0, height: 3.0 },
Shape::Triangle(3.0, 4.0, 5.0),
];
for shape in &shapes {
println!("{} 的面积是 {:.2}", shape.describe(), shape.area());
}
}
6.4 Option<T> —— 告别 null 和 undefined
6.4.1 JS/TS 中的 null 问题
// TypeScript - null 和 undefined 是所有问题的根源之一
function findUser(id: number): User | null {
// 可能返回 null
return null;
}
const user = findUser(1);
// 如果忘记检查 null...
console.log(user.name); // 💥 运行时错误:Cannot read property 'name' of null
// 即使开启了 strictNullChecks,也容易忘记处理
Tony Hoare(null 的发明者)称 null 为他的”十亿美元错误”。
6.4.2 Rust 的 Option<T>
Rust 中没有 null。取而代之的是 Option<T> 枚举:
// Option 的定义(标准库中)
enum Option<T> {
Some(T), // 有值
None, // 没有值
}
// Option 太常用了,所以 Some 和 None 不需要 Option:: 前缀
fn main() {
let some_number: Option<i32> = Some(42);
let no_number: Option<i32> = None;
// 你不能把 Option<i32> 当作 i32 使用!
// let result = some_number + 1; // ❌ 编译错误
// 必须先处理 None 的情况
}
6.4.3 Option vs TypeScript 的 T | null | undefined
┌──────────────────────────────────────────────────────────────┐
│ Option<T> vs T | null | undefined │
├──────────────────────────┬───────────────────────────────────┤
│ Rust: Option<T> │ TS: T | null | undefined │
├──────────────────────────┼───────────────────────────────────┤
│ Some(value) 或 None │ value 或 null 或 undefined │
│ 编译时强制处理 None │ 可能忘记检查 null(运行时爆炸) │
│ 类型安全:Option<i32> │ null 可以赋给几乎任何类型 │
│ 不能当 T 直接使用 │ TS 可能不够严格 │
│ 必须用 match/unwrap 等 │ 可以直接 .property(可能爆炸) │
│ 零成本抽象 │ 运行时检查 │
└──────────────────────────┴───────────────────────────────────┘
6.4.4 处理 Option 的常用方法
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("动动"))
} else {
None
}
}
fn main() {
let user = find_user(1);
// 方法 1:match(最基础)
match user {
Some(name) => println!("找到用户: {}", name),
None => println!("用户不存在"),
}
// 方法 2:if let(只关心 Some 的情况)
let user = find_user(1);
if let Some(name) = user {
println!("找到用户: {}", name);
}
// 方法 3:unwrap(确信是 Some,否则 panic)
let user = find_user(1);
let name = user.unwrap(); // ⚠️ 如果是 None 会 panic!
println!("用户: {}", name);
// 方法 4:unwrap_or(提供默认值)
let user = find_user(999);
let name = user.unwrap_or(String::from("匿名用户"));
println!("用户: {}", name); // "匿名用户"
// 方法 5:unwrap_or_else(惰性默认值)
let user = find_user(999);
let name = user.unwrap_or_else(|| {
println!("计算默认值...");
String::from("默认用户")
});
// 方法 6:map(转换 Some 中的值)
let user = find_user(1);
let greeting = user.map(|name| format!("你好,{}!", name));
println!("{:?}", greeting); // Some("你好,动动!")
// 方法 7:and_then(链式操作,类似 flatMap)
let result = find_user(1)
.map(|name| name.len()) // Option<usize>
.filter(|&len| len > 3) // 过滤
.map(|len| format!("名字长度: {}", len));
println!("{:?}", result);
// 方法 8:? 运算符(在返回 Option 的函数中)
// 见下一节
}
6.4.5 ? 运算符 —— 优雅的错误传播
// ? 运算符:如果是 None,直接返回 None;如果是 Some,取出值继续
fn get_user_email_domain(id: u32) -> Option<String> {
let name = find_user(id)?; // 如果 None,直接返回 None
let email = find_email(&name)?; // 同上
let domain = email.split('@').nth(1)?; // 同上
Some(domain.to_string())
}
fn find_user(id: u32) -> Option<String> {
if id == 1 { Some(String::from("动动")) } else { None }
}
fn find_email(name: &str) -> Option<String> {
if name == "动动" {
Some(String::from("dong@example.com"))
} else {
None
}
}
fn main() {
// 如果任何步骤返回 None,整个函数返回 None
match get_user_email_domain(1) {
Some(domain) => println!("域名: {}", domain),
None => println!("找不到域名"),
}
}
对比 TypeScript 的可选链:
// TypeScript 的 ?. 运算符类似,但在运行时检查
const domain = users
?.find(u => u.id === 1)
?.email
?.split('@')[1];
// domain 的类型是 string | undefined
6.5 模式匹配(Pattern Matching)深入
6.5.1 match 表达式
match 是 Rust 中最强大的控制流工具之一:
fn main() {
let number = 13;
// match 是一个表达式,有返回值
let description = match number {
1 => "一",
2 | 3 | 5 | 7 | 11 | 13 => "质数", // | 表示"或"
4..=12 => "4到12之间", // ..= 表示范围
13..=19 => "青少年",
_ => "其他", // _ 是通配符
};
println!("{} 是 {}", number, description);
}
6.5.2 match 与枚举的完美配合
#[derive(Debug)]
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 25 美分硬币上有州的标记
}
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
California,
// ...
}
fn value_in_cents(coin: &Coin) -> u32 {
match coin {
Coin::Penny => {
println!("幸运便士!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("这是来自 {:?} 州的 25 美分!", state);
25
}
}
}
fn main() {
let coins = vec![
Coin::Penny,
Coin::Quarter(UsState::California),
Coin::Dime,
];
let total: u32 = coins.iter().map(|c| value_in_cents(c)).sum();
println!("总价值: {} 美分", total);
}
6.5.3 match 必须穷尽所有情况
enum Color {
Red,
Green,
Blue,
}
fn describe_color(color: Color) -> &'static str {
match color {
Color::Red => "红色",
Color::Green => "绿色",
// ❌ 编译错误!没有处理 Color::Blue
// Rust 要求你处理所有可能的情况
}
}
// ✅ 正确:处理所有情况
fn describe_color_v2(color: Color) -> &'static str {
match color {
Color::Red => "红色",
Color::Green => "绿色",
Color::Blue => "蓝色",
}
}
// ✅ 或者用 _ 通配符处理剩余情况
fn is_red(color: Color) -> bool {
match color {
Color::Red => true,
_ => false, // 其他所有情况
}
}
💡 这就是 Rust 枚举的安全保证 —— 你不可能忘记处理某个情况。如果后来添加了新的枚举变体,所有
match都会编译失败,提醒你更新代码。
6.5.4 if let —— 简洁的单一模式匹配
当你只关心一种情况时,if let 比完整的 match 更简洁:
fn main() {
let config_value: Option<u32> = Some(3);
// 用 match(稍显冗余)
match config_value {
Some(val) => println!("配置值: {}", val),
None => (), // 什么都不做... 但必须写
}
// 用 if let(更简洁)
if let Some(val) = config_value {
println!("配置值: {}", val);
}
// if let 也可以有 else
if let Some(val) = config_value {
println!("配置值: {}", val);
} else {
println!("没有配置");
}
// while let —— 循环匹配
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("弹出: {}", top);
}
// 输出: 3, 2, 1
}
6.5.5 let else —— 提前返回模式
// Rust 1.65+ 引入的 let else 语法
fn process_config(config: Option<String>) -> String {
// 如果是 None,直接返回
let Some(value) = config else {
return String::from("使用默认配置");
};
// 这里 value 已经是 String(不是 Option<String>)
format!("使用配置: {}", value)
}
fn main() {
println!("{}", process_config(Some(String::from("custom"))));
println!("{}", process_config(None));
}
6.6 常用模式与最佳实践
6.6.1 构建者模式(Builder Pattern)
struct HttpRequest {
url: String,
method: String,
headers: Vec<(String, String)>,
body: Option<String>,
}
struct HttpRequestBuilder {
url: String,
method: String,
headers: Vec<(String, String)>,
body: Option<String>,
}
impl HttpRequestBuilder {
fn new(url: &str) -> HttpRequestBuilder {
HttpRequestBuilder {
url: url.to_string(),
method: String::from("GET"),
headers: Vec::new(),
body: None,
}
}
fn method(mut self, method: &str) -> HttpRequestBuilder {
self.method = method.to_string();
self // 返回 self 以支持链式调用
}
fn header(mut self, key: &str, value: &str) -> HttpRequestBuilder {
self.headers.push((key.to_string(), value.to_string()));
self
}
fn body(mut self, body: &str) -> HttpRequestBuilder {
self.body = Some(body.to_string());
self
}
fn build(self) -> HttpRequest {
HttpRequest {
url: self.url,
method: self.method,
headers: self.headers,
body: self.body,
}
}
}
fn main() {
// 链式调用,非常优雅
let request = HttpRequestBuilder::new("https://api.example.com/users")
.method("POST")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.body(r#"{"name": "动动"}"#)
.build();
println!("请求: {} {}", request.method, request.url);
}
6.6.2 新类型模式(Newtype Pattern)
// 用元组结构体包装基本类型,增加类型安全
struct Meters(f64);
struct Kilometers(f64);
struct Miles(f64);
impl Meters {
fn to_kilometers(&self) -> Kilometers {
Kilometers(self.0 / 1000.0)
}
fn to_miles(&self) -> Miles {
Miles(self.0 / 1609.34)
}
}
fn main() {
let distance = Meters(42195.0); // 马拉松距离
// 不能把 Meters 和 Kilometers 搞混!
let km = distance.to_kilometers();
let mi = distance.to_miles();
println!("马拉松: {:.2}m = {:.2}km = {:.2}mi",
distance.0, km.0, mi.0);
// 类型安全:不能把 Meters 当 Kilometers 用
// let wrong: Kilometers = distance; // ❌ 类型不匹配
}
6.6.3 状态机模式
// 用枚举表示状态,编译器保证状态转换的正确性
enum TrafficLight {
Red,
Yellow,
Green,
}
impl TrafficLight {
fn next(self) -> TrafficLight {
match self {
TrafficLight::Red => TrafficLight::Green,
TrafficLight::Green => TrafficLight::Yellow,
TrafficLight::Yellow => TrafficLight::Red,
}
}
fn duration_seconds(&self) -> u32 {
match self {
TrafficLight::Red => 60,
TrafficLight::Yellow => 5,
TrafficLight::Green => 45,
}
}
fn display(&self) -> &str {
match self {
TrafficLight::Red => "🔴 红灯",
TrafficLight::Yellow => "🟡 黄灯",
TrafficLight::Green => "🟢 绿灯",
}
}
}
fn main() {
let mut light = TrafficLight::Red;
for _ in 0..6 {
println!("{} ({}秒)", light.display(), light.duration_seconds());
light = light.next();
}
}
6.6.4 用枚举替代继承
// TypeScript 中可能用继承/接口:
// abstract class Animal { abstract speak(): string; }
// class Dog extends Animal { speak() { return "汪!"; } }
// class Cat extends Animal { speak() { return "喵!"; } }
// Rust 中用枚举:
enum Animal {
Dog { name: String, breed: String },
Cat { name: String, indoor: bool },
Fish { name: String, water_type: WaterType },
}
enum WaterType {
Fresh,
Salt,
}
impl Animal {
fn speak(&self) -> &str {
match self {
Animal::Dog { .. } => "汪汪!",
Animal::Cat { .. } => "喵喵!",
Animal::Fish { .. } => "...",
}
}
fn name(&self) -> &str {
match self {
Animal::Dog { name, .. } => name,
Animal::Cat { name, .. } => name,
Animal::Fish { name, .. } => name,
}
}
fn describe(&self) -> String {
match self {
Animal::Dog { name, breed } =>
format!("{} 是一只 {} 狗", name, breed),
Animal::Cat { name, indoor } =>
format!("{} 是一只{}猫", name, if *indoor { "室内" } else { "室外" }),
Animal::Fish { name, water_type } => {
let water = match water_type {
WaterType::Fresh => "淡水",
WaterType::Salt => "海水",
};
format!("{} 是一条{}鱼", name, water)
}
}
}
}
fn main() {
let animals = vec![
Animal::Dog {
name: String::from("旺财"),
breed: String::from("金毛"),
},
Animal::Cat {
name: String::from("咪咪"),
indoor: true,
},
Animal::Fish {
name: String::from("尼莫"),
water_type: WaterType::Salt,
},
];
for animal in &animals {
println!("{}: {} {}", animal.name(), animal.speak(), animal.describe());
}
}
6.7 Result<T, E> —— 错误处理预览
Result 和 Option 类似,但用于可能出错的操作:
// Result 的定义(标准库中)
enum Result<T, E> {
Ok(T), // 成功,携带结果
Err(E), // 失败,携带错误
}
use std::fs;
fn read_config() -> Result<String, std::io::Error> {
// ? 运算符:如果是 Err,直接返回 Err
let content = fs::read_to_string("config.toml")?;
Ok(content)
}
fn main() {
match read_config() {
Ok(content) => println!("配置内容:\n{}", content),
Err(e) => println!("读取配置失败: {}", e),
}
// 或者用 unwrap_or_else
let config = read_config().unwrap_or_else(|e| {
println!("使用默认配置 (错误: {})", e);
String::from("default = true")
});
}
💡
Result将在后续的错误处理章节中详细讲解。现在只需知道它是Option的”增强版” ——Option表示”有或没有”,Result表示”成功或失败(附带错误信息)“。
6.8 练习题
练习 1:定义和使用结构体
// 定义一个 Song 结构体,包含以下字段:
// - title: 歌曲名
// - artist: 歌手名
// - duration_secs: 时长(秒)
// - play_count: 播放次数
//
// 实现以下方法:
// - new(title, artist, duration_secs) -> Song
// - play(&mut self) - 增加播放次数
// - duration_display(&self) -> String - 返回 "MM:SS" 格式
// - is_long(&self) -> bool - 超过 5 分钟算长歌
//
// 实现关联函数:
// - most_played(songs: &[Song]) -> Option<&Song>
// 在这里写你的代码
<details> <summary>📝 答案</summary>
#[derive(Debug)]
struct Song {
title: String,
artist: String,
duration_secs: u32,
play_count: u32,
}
impl Song {
fn new(title: &str, artist: &str, duration_secs: u32) -> Song {
Song {
title: title.to_string(),
artist: artist.to_string(),
duration_secs,
play_count: 0,
}
}
fn play(&mut self) {
self.play_count += 1;
println!("🎵 正在播放: {} - {}", self.title, self.artist);
}
fn duration_display(&self) -> String {
let minutes = self.duration_secs / 60;
let seconds = self.duration_secs % 60;
format!("{:02}:{:02}", minutes, seconds)
}
fn is_long(&self) -> bool {
self.duration_secs > 300
}
fn most_played(songs: &[Song]) -> Option<&Song> {
songs.iter().max_by_key(|s| s.play_count)
}
}
fn main() {
let mut playlist = vec![
Song::new("晴天", "周杰伦", 269),
Song::new("Bohemian Rhapsody", "Queen", 355),
Song::new("稻香", "周杰伦", 223),
];
playlist[0].play();
playlist[0].play();
playlist[1].play();
for song in &playlist {
println!("{} [{}] 播放 {} 次 {}",
song.title,
song.duration_display(),
song.play_count,
if song.is_long() { "(长歌)" } else { "" });
}
if let Some(top) = Song::most_played(&playlist) {
println!("最多播放: {}", top.title);
}
}
</details>
练习 2:设计枚举
// 设计一个表示 JSON 值的枚举 JsonValue
// JSON 可以是:
// - null
// - 布尔值
// - 数字(用 f64)
// - 字符串
// - 数组(Vec<JsonValue>)
// - 对象(Vec<(String, JsonValue)>,简化实现)
//
// 实现方法:
// - is_null(&self) -> bool
// - as_string(&self) -> Option<&str>
// - as_number(&self) -> Option<f64>
// - display(&self) -> String(简单的 JSON 格式化)
<details> <summary>📝 答案</summary>
#[derive(Debug)]
enum JsonValue {
Null,
Bool(bool),
Number(f64),
Str(String),
Array(Vec<JsonValue>),
Object(Vec<(String, JsonValue)>),
}
impl JsonValue {
fn is_null(&self) -> bool {
matches!(self, JsonValue::Null)
}
fn as_string(&self) -> Option<&str> {
match self {
JsonValue::Str(s) => Some(s),
_ => None,
}
}
fn as_number(&self) -> Option<f64> {
match self {
JsonValue::Number(n) => Some(*n),
_ => None,
}
}
fn display(&self) -> String {
match self {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => n.to_string(),
JsonValue::Str(s) => format!("\"{}\"", s),
JsonValue::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| v.display()).collect();
format!("[{}]", items.join(", "))
}
JsonValue::Object(obj) => {
let items: Vec<String> = obj.iter()
.map(|(k, v)| format!("\"{}\": {}", k, v.display()))
.collect();
format!("{{{}}}", items.join(", "))
}
}
}
}
fn main() {
let json = JsonValue::Object(vec![
("name".to_string(), JsonValue::Str("动动".to_string())),
("age".to_string(), JsonValue::Number(28.0)),
("active".to_string(), JsonValue::Bool(true)),
("scores".to_string(), JsonValue::Array(vec![
JsonValue::Number(90.0),
JsonValue::Number(85.0),
JsonValue::Number(95.0),
])),
("address".to_string(), JsonValue::Null),
]);
println!("{}", json.display());
if let JsonValue::Object(fields) = &json {
for (key, value) in fields {
println!(" {} = {}", key, value.display());
}
}
}
</details>
练习 3:Option 链式操作
// 实现一个简单的用户系统
// 功能:查找用户 → 获取邮箱 → 获取域名 → 判断是否为公司邮箱
struct User {
name: String,
email: Option<String>,
}
fn find_user(users: &[User], name: &str) -> Option<&User> {
users.iter().find(|u| u.name == name)
}
// 实现这个函数:判断用户是否使用公司邮箱(非 gmail/qq/163)
fn is_corporate_email(users: &[User], name: &str) -> Option<bool> {
// 使用 ? 运算符或链式方法
todo!()
}
fn main() {
let users = vec![
User { name: "动动".to_string(), email: Some("dong@company.com".to_string()) },
User { name: "小羊".to_string(), email: Some("yang@gmail.com".to_string()) },
User { name: "小明".to_string(), email: None },
];
for name in &["动动", "小羊", "小明", "不存在"] {
match is_corporate_email(&users, name) {
Some(true) => println!("{}: 使用公司邮箱 ✅", name),
Some(false) => println!("{}: 使用个人邮箱 ❌", name),
None => println!("{}: 无法确定 ❓", name),
}
}
}
<details> <summary>📝 答案</summary>
fn is_corporate_email(users: &[User], name: &str) -> Option<bool> {
let user = find_user(users, name)?;
let email = user.email.as_ref()?;
let domain = email.split('@').nth(1)?;
let personal_domains = ["gmail.com", "qq.com", "163.com", "outlook.com"];
Some(!personal_domains.contains(&domain))
}
</details>
练习 4:match 穷尽匹配
// 实现一个命令行计算器的命令解析器
enum Command {
Add(f64, f64),
Subtract(f64, f64),
Multiply(f64, f64),
Divide(f64, f64),
Quit,
Help,
}
// 实现 execute 方法,返回 Option<f64>
// Quit 和 Help 返回 None
// 除法除以 0 时返回 None
impl Command {
fn execute(&self) -> Option<f64> {
todo!()
}
fn describe(&self) -> String {
todo!()
}
}
fn main() {
let commands = vec![
Command::Add(10.0, 5.0),
Command::Subtract(10.0, 3.0),
Command::Multiply(4.0, 7.0),
Command::Divide(10.0, 3.0),
Command::Divide(10.0, 0.0),
Command::Help,
Command::Quit,
];
for cmd in &commands {
print!("{}: ", cmd.describe());
match cmd.execute() {
Some(result) => println!("= {:.2}", result),
None => println!("(无结果)"),
}
}
}
<details> <summary>📝 答案</summary>
impl Command {
fn execute(&self) -> Option<f64> {
match self {
Command::Add(a, b) => Some(a + b),
Command::Subtract(a, b) => Some(a - b),
Command::Multiply(a, b) => Some(a * b),
Command::Divide(a, b) => {
if *b == 0.0 {
None
} else {
Some(a / b)
}
}
Command::Quit => None,
Command::Help => {
println!("可用命令: add, subtract, multiply, divide, quit");
None
}
}
}
fn describe(&self) -> String {
match self {
Command::Add(a, b) => format!("{} + {}", a, b),
Command::Subtract(a, b) => format!("{} - {}", a, b),
Command::Multiply(a, b) => format!("{} × {}", a, b),
Command::Divide(a, b) => format!("{} ÷ {}", a, b),
Command::Quit => "退出".to_string(),
Command::Help => "帮助".to_string(),
}
}
}
</details>
6.9 本章小结
┌──────────────────────────────────────────────────────────┐
│ 本章知识点回顾 │
├──────────────────────────────────────────────────────────┤
│ │
│ 🏗️ 结构体(struct) │
│ 类似 TS 的 interface + class │
│ 数据(struct)和行为(impl)分离 │
│ │
│ 🔧 impl 块 │
│ &self(只读)/ &mut self(修改)/ self(消费) │
│ 关联函数(无 self,类似 static) │
│ │
│ 🎭 枚举(enum) │
│ 比 TS 的 enum 强大得多 │
│ 每个变体可携带不同数据 │
│ 类似 TS 的 discriminated union │
│ │
│ ❓ Option<T> │
│ 替代 null/undefined │
│ 编译时强制处理 None │
│ 丰富的方法:map, and_then, unwrap_or, ? │
│ │
│ 🎯 match 表达式 │
│ 穷尽匹配(编译器保证) │
│ 模式匹配 + 解构 │
│ if let / while let / let else │
│ │
│ 🏆 常用模式 │
│ 构建者模式、新类型模式、状态机模式 │
│ │
└──────────────────────────────────────────────────────────┘
下一章预告:我们将学习 Rust 的错误处理体系 ——
Result<T, E>、自定义错误类型和?运算符。告别 try/catch,迎接更安全的错误处理方式!
📖 推荐阅读:The Rust Programming Language - 结构体 | The Rust Programming Language - 枚举 | course.rs - 复合类型