第四章:复合数据类型
结构体、枚举与集合类型是 Rust 构建复杂数据模型的核心工具。这一章你将学会如何定义自定义类型、利用模式匹配优雅地解构数据,以及选用合适的标准集合来高效组织数据。
4.1 结构体(struct)
结构体是自定义数据类型的基本方式,可以把多个相关的值打包在一起,并为每个字段命名,让代码意涵更清晰。
定义与实例化
定义一个结构体:
struct User {
active: bool,
username: String,
sign_in_count: u64,
}
创建实例时,字段顺序不必与定义一致,但所有字段都必须赋值:
let user1 = User {
active: true,
username: String::from("alice"),
sign_in_count: 1,
};
通过点号访问字段:
println!("{}", user1.username);
只有把整个实例声明为 mut 才能修改字段,Rust 不允许只将部分字段标记为可变。
let mut user2 = User {
active: true,
username: String::from("bob"),
sign_in_count: 2,
};
user2.sign_in_count += 1;
字段初始化简写与结构体更新语法
如果变量名和字段名相同,可以省略冒号和值:
fn build_user(username: String) -> User {
User {
active: true,
username, // 简写
sign_in_count: 1,
}
}
想基于已有实例创建新实例,只修改部分字段时,使用 .. 更新语法:
let user3 = User {
username: String::from("charlie"),
..user2 // 其余字段使用 user2 的值
};
注意:..user2 会移动 user2 中未实现 Copy 的字段(比如 String),此后 user2 不能再整体使用,但未移动的字段依然有效。
元组结构体与单元结构体
元组结构体 没有字段名,只有类型:
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
println!("{}", black.0); // 通过索引访问
单元结构体 不保存任何数据,仅作为标记类型,常用于实现 trait:
struct AlwaysEqual;
let subject = AlwaysEqual;
方法与关联函数
impl 块为结构体定义方法。方法的第一个参数是 self 的某种形式:
&self:不可变借用,读取数据。&mut self:可变借用,修改数据。self:获取所有权,消耗结构体。
impl User {
fn deactivate(&mut self) {
self.active = false;
}
}
关联函数 不以 self 为参数,通常用作构造函数,调用时用 :::
impl User {
fn new(username: String) -> Self {
User {
active: true,
username,
sign_in_count: 1,
}
}
}
let u = User::new(String::from("dave"));
调试输出
通过为结构体添加 #[derive(Debug)] 属性,可以自动实现 Debug trait,方便打印调试信息:
#[derive(Debug)]
struct User { /* ... */ }
println!("{:?}", user1);
4.2 枚举(enum)与模式匹配
枚举让你定义一个类型,其值可以是几个固定变体之一,且每个变体可以携带不同类型和大小的数据,比很多语言中的联合体强大得多。
枚举定义
一个典型的消息枚举:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
每个变体可以:
- 无数据(
Quit) - 带匿名字段(
Write(String)) - 带命名字段(
Move { x, y })
枚举的强大之处在于:一个值可以是这些变体中的任何一个,但处理它时必须通过模式匹配穷尽所有可能。
Option<T>:用类型系统取代空指针
Option 是标准库预定义的枚举,表达了“要么有值,要么什么都没有”:
enum Option<T> {
None,
Some(T),
}
你不需要手动 use,Option::Some 和 Option::None 直接可用。对一个 Option,必须显式处理 None 情况,从编译期避免空指针问题:
let x: Option<i32> = Some(5);
let y: Option<i32> = None;
Result<T, E>:错误处理的核心
Result 表示可能失败的操作:
enum Result<T, E> {
Ok(T),
Err(E),
}
处理 Result 时也要面对成功和失败两条路径:
let f = std::fs::File::open("file.txt");
match f {
Ok(file) => { /* 使用文件 */ }
Err(error) => { /* 处理错误 */ }
}
后续章节会深入 Result 的组合子和传播方式。
match 表达式
match 与枚举天然契合。它会强制检查所有变体(穷尽性检查),每个分支叫一个“臂”(arm)。模式可以包含解构、或运算符、范围、守卫等。
match msg {
Message::Quit => println!("退出"),
Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
Message::Write(text) => println!("写入: {}", text),
Message::ChangeColor(r, g, b) => println!("改变颜色: ({}, {}, {})", r, g, b),
}
多模式与范围
用 | 在一个臂中匹配多个模式:
match x {
1 | 2 => println!("一或二"),
_ => println!("其他"),
}
匹配范围(要求值类型支持):
match x {
1..=5 => println!("1到5"),
_ => println!("其他"),
}
匹配守卫
在模式后加上 if 条件来进一步过滤:
match some_number {
n if n % 2 == 0 => println!("偶数"),
_ => println!("奇数"),
}
if let 和 while let
如果只关心一种模式,可以使用更简洁的 if let:
if let Message::Write(text) = msg {
println!("写入 {text}");
} else {
println!("不是写入消息");
}
while let 则持续匹配,常用于迭代器或通道:
while let Some(top) = stack.pop() {
println!("{top}");
}
解构
模式匹配的强大离不开解构。你可以随手拆开复合数据。
元组解构:
let pair = (1, "hello");
let (x, y) = pair;
println!("{} {}", x, y);
结构体解构:
struct Point { x: i32, y: i32 }
let p = Point { x: 10, y: 20 };
let Point { x, y } = p;
也可以部分解构或重命名:
let Point { x: a, y: b } = p; // a == 10, b == 20
4.3 集合类型
Rust 标准库提供了多种通用集合类型,它们分配在堆上,可以动态增长。
Vec<T>:动态数组
Vec 是最常用的集合,可以存储一系列相同类型的值,按需自动扩容。
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
使用 vec! 宏创建并初始化:
let v = vec![1, 2, 3, 4, 5];
访问元素有两种方式:
- 索引
&v[2]:越界时会 panic。 v.get(2)返回Option<&T>,安全处理。
match v.get(10) {
Some(value) => println!("{value}"),
None => println!("没有这个元素"),
}
遍历:
for i in &v {
println!("{i}");
}
for i in &mut v {
*i += 1; // 通过解引用指针修改值
}
Vec 可以通过切片视图访问其全部或部分内容:
let slice: &[i32] = &v[1..3];
String 与 &str
Rust 的字符串相当严谨。String 是一个可增长的、拥有所有权的 UTF-8 编码字符串,分配在堆上。
let mut s = String::from("hello");
s.push_str(", world");
s.push('!');
用 + 拼接时,左侧必须是 String,右侧是 &str,且会获取左侧的所有权:
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = s1 + &s2; // s1 所有权被移走,此后不可再用 s1
// println!("{s1}"); // 编译错误
format! 按引用格式化,不拿走所有权,更灵活:
let hello = String::from("hello");
let world = String::from("world");
let s = format!("{}-{}", hello, world); // hello、world 仍可使用
&str 是字符串切片,本质是对内存某段有效 UTF-8 数据的引用。字符串字面量就是 &str:
let literal: &str = "I'm static";
许多 API 使用 &str 作为参数,因为 &String 会自动强制转换为 &str,让接口更加通用。
HashMap<K, V>
哈希映射存储键值对,键类型必须实现 Eq 和 Hash trait。
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
通过 get 按键取值,返回 Option<&V>:
let team = String::from("Blue");
let score = scores.get(&team).copied().unwrap_or(0);
更新值的方法:
insert会覆盖旧值。entryAPI 根据键是否存在,决定插入还是修改:
scores.entry(String::from("Blue")).or_insert(50); // 存在则不动
scores.entry(String::from("Red")).or_insert(30); // 不存在则插入
用 entry 返回的可变引用还可以对旧值进行更新:
let count = scores.entry(String::from("Blue")).or_insert(0);
*count += 10;
遍历:
for (key, value) in &scores {
println!("{key}: {value}");
}
其他集合类型
Rust 提供的最常用集合还包括:
HashSet<T>:基于HashMap的集合,保证元素唯一,提供高效的插入、查询和删除。VecDeque<T>:双端队列,可在头尾高效地插入和移除,内部是环形缓冲。LinkedList<T>:双向链表,适合在任意位置频繁插入、分割列表的场景。但在连续内存遍历方面性能不如VecDeque。BinaryHeap<T>:基于二叉堆的最大优先队列,peek返回最大元素。
选择集合时要考虑访问模式、性能特性和内存布局。
迭代器(Iterator)初步
迭代器是处理集合的统一抽象,Rust 的迭代器零开销且功能极其强大。
三种产生迭代器的常用方法:
.iter():返回不可变引用的迭代器,不消耗集合。.iter_mut():返回可变引用的迭代器。.into_iter():获取所有权,消耗集合并产生元素。
let v = vec![1, 2, 3];
for val in v.iter() {
println!("{val}");
}
// v 仍然可用
迭代器可以链式调用各种适配器(map、filter、collect 等),后续章节将详细介绍。这里只需知道:for 循环本质上是 into_iter() 的语法糖,而标准库中的集合都实现了 IntoIterator,让它们能自然地在循环中工作。
结构体让你把数据组织成有意义的整体,枚举使类型能承载多种可能状态,而集合则负责高效存储和遍历成批数据。下一章请阅读 泛型与 Trait。
参考文献
| 资料 | 说明 |
|---|---|
| Structs | 官方书第 5 章 |
| Enums | 枚举与 match |
| Collections | Vec、String、HashMap |
相关文章
第八章:测试、文档与质量
Rust 将测试和文档视为语言的一等公民。通过内置的测试框架,你可以在项目里编写单元测试、集成测试以及随文档一起运行的示例测试,三者共用 cargo test 命令。配合 cargo doc 生成文档,形成了一套确保代码质量与可维护性…
泛型与 Trait
泛型和 trait 是 Rust 实现代码复用与多态的两大支柱。前置:Rust 基础。泛型让代码可以工作在多种类型上而不牺牲性能,trait 定义了类型间的共享行为,二者结合形成了零成本抽象的强大表达能力。本章将深入泛型定义、trai…
第二章:基础语法与类型
Rust 的类型系统和语法设计处处体现着“安全”与“显式”的理念。这一章你将掌握变量绑定、基本类型、复合类型、函数定义以及所有基础控制流结构,它们是你写出任何 Rust 程序的基石。
第七章:模块系统与包管理
Rust 的模块系统为代码组织、封装和复用提供了一套严谨但灵活的机制。包、crate、模块以及 use 路径相互配合,让你能够把项目拆解成清晰的功能单元,同时精确控制哪些对外可见。本章将带你系统掌握这些构建大型 Rust 项目所必需的…
第十章:进阶特性与模式
Rust 的核心安全保证覆盖了绝大多数日常编程场景。但当你需要打破常规——无论是编写极致通用的抽象、与 C 库交互,还是内联优化——本章将带你进入 Rust 的深层能力:声明宏与过程宏、unsafe 的超能力与封装、高级类型系统技巧…
第五章:错误处理
Rust 将错误明确分为两类:不可恢复的错误与可恢复的错误。通过 panic! 处理前一种,Result 处理后一种,这让程序的错误路径不再是隐式的控制流,而是强类型、必须处理的代码分支。配合 Option 对缺失值的处理以及丰富的组…
Series
rust
4 / 10