第二章:数据模型设计与自定义类型
教学目标
- 掌握结构体(Struct)与枚举(Enum)的定义与使用方法
- 理解 Rust 中方法实现(impl 块)与类型系统的核心机制
- 完成 RustTask 项目中任务数据模型的设计与基础功能实现
核心知识点
1. 结构体(Struct)
结构体定义与实例化
结构体是 Rust 中用于组合多个相关数据的自定义类型,类似于其他语言中的类或结构体。定义结构体时需要使用struct关键字,后跟结构体名称和包含的字段,字段格式为字段名: 类型。
// 定义一个Person结构体,包含姓名、年龄和是否为学生三个字段
struct Person {
name: String,
age: u8,
is_student: bool,
}
fn main() {
// 通过字段初始化语法实例化结构体
let person = Person {
name: "张三".to_string(),
age: 20,
is_student: true,
};
// 访问结构体字段
println!("姓名: {}, 年龄: {}, 学生: {}",
person.name, person.age, person.is_student);
}
结构体更新语法(..)
Rust 提供了简洁的结构体更新语法,允许基于现有结构体实例快速创建新实例,未显式指定的字段将沿用原实例的值。
struct Person {
name: String,
age: u8,
is_student: bool,
}
fn main() {
let person1 = Person {
name: "张三".to_string(),
age: 20,
is_student: true,
};
// 使用..更新语法创建新实例,仅修改name和is_student字段
let person2 = Person {
name: "李四".to_string(),
is_student: false,
..person1 // 沿用person1的age字段
};
println!("person2年龄: {}", person2.age); // 输出: 20
}
结构体派生宏(#[derive (Debug)]
Rust 的派生宏可以为结构体自动实现一些常用的 trait,Debug trait 允许使用{:?}格式符打印结构体内容,便于调试。
// 使用#[derive(Debug)]派生Debug trait
#[derive(Debug)]
struct Person {
name: String,
age: u8,
is_student: bool,
}
fn main() {
let person = Person {
name: "张三".to_string(),
age: 20,
is_student: true,
};
// 使用{:?}打印结构体,需要先派生Debug trait
println!("person: {:?}", person); // 输出: person: Person { name: "张三", age: 20, is_student: true }
}
2. 枚举(Enum)
基础枚举类型定义与模式匹配
枚举用于定义一组可能的取值,每个取值称为一个变体。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!("向右"),
}
}
带数据的枚举变体
枚举变体可以携带数据,类似其他语言中的联合类型,每个变体可以有不同的数据类型和数量。
// 定义消息枚举,不同变体携带不同类型的数据
enum Message {
// 无数据变体
Quit,
// 携带一个i32数据
Move(i32, i32),
// 携带一个String数据
Write(String),
// 携带两个i32和一个bool数据
ChangeColor(i32, i32, bool),
}
fn main() {
let msg1 = Message::Quit;
let msg2 = Message::Move(10, 20);
let msg3 = Message::Write("Hello".to_string());
let msg4 = Message::ChangeColor(255, 0, 0, false);
// 匹配不同的消息变体并处理对应数据
match msg2 {
Message::Quit => println!("退出程序"),
Message::Move(x, y) => println!("移动到坐标: ({}, {})", x, y),
Message::Write(text) => println!("写入文本: {}", text),
Message::ChangeColor(r, g, b, bold) => {
println!("修改颜色为RGB({}, {}, {}), 加粗: {}", r, g, b, bold)
}
}
}
Option 与 Result 枚举的基本使用
Rust 通过Option和Result<T, E>枚举处理可能不存在的值和操作错误,是 Rust 错误处理机制的核心。
fn main() {
// Option<T>表示可能存在或不存在的值
let some_num = Some(5);
let no_num: Option<i32> = None;
// 使用match处理Option值
match some_num {
Some(num) => println!("存在值: {}", num),
None => println!("没有值"),
}
// Result<T, E>表示操作可能成功或失败
let result = divide(10, 2);
println!("10 / 2 = {}", result.unwrap()); // 输出: 5
let result = divide(10, 0);
match result {
Ok(num) => println!("结果: {}", num),
Err(msg) => println!("错误: {}", msg),
}
}
// 除法函数,返回Result<i32, &'static str>
fn divide(dividend: i32, divisor: i32) -> Result<i32, &'static str> {
if divisor == 0 {
Err("除数不能为零")
} else {
Ok(dividend / divisor)
}
}
3. 方法实现(impl 块)
为结构体定义关联函数(fn new)
impl块用于为结构体或枚举实现方法,关联函数(不带self参数的方法)通常用于创建结构体实例,类似构造函数。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 关联函数,用于创建Rectangle实例
fn new(width: u32, height: u32) -> Self {
Rectangle { width, height }
}
// 实例方法,计算矩形面积
fn area(&self) -> u32 {
self.width * self.height
}
// 可变实例方法,修改矩形尺寸
fn set_size(&mut self, width: u32, height: u32) {
self.width = width;
self.height = height;
}
}
fn main() {
// 使用关联函数创建矩形实例
let rect = Rectangle::new(10, 20);
println!("矩形面积: {}", rect.area()); // 输出: 200
let mut rect2 = Rectangle::new(5, 5);
rect2.set_size(10, 10);
println!("修改后矩形面积: {}", rect2.area()); // 输出: 100
}
实例方法与可变引用
实例方法通过&self(不可变引用)或&mut self(可变引用)接收结构体实例,&self允许方法读取实例数据,&mut self允许方法修改实例数据。
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
// 不可变引用方法,读取计数值
fn get_count(&self) -> u32 {
self.count
}
// 可变引用方法,增加计数值
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut counter = Counter::new();
println!("初始计数: {}", counter.get_count()); // 输出: 0
counter.increment();
println!("计数+1: {}", counter.get_count()); // 输出: 1
counter.increment();
println!("计数+1: {}", counter.get_count()); // 输出: 2
}
方法链式调用
Rust 支持方法的链式调用,当方法返回self或&mut self时,可以连续调用多个方法。
struct StringBuilder {
content: String,
}
impl StringBuilder {
fn new() -> Self {
StringBuilder { content: String::new() }
}
// 返回&mut self,支持链式调用
fn append(&mut self, text: &str) -> &mut Self {
self.content.push_str(text);
self
}
// 返回String,结束链式调用
fn build(self) -> String {
self.content
}
}
fn main() {
let result = StringBuilder::new()
.append("Hello, ")
.append("Rust!")
.build();
println!("{}", result); // 输出: Hello, Rust!
}
项目实战:设计任务数据模型
1. 定义 TaskStatus 枚举表示任务状态
首先在src目录下创建task.rs文件,用于存放任务相关的类型定义和方法实现。在task.rs中定义TaskStatus枚举,表示任务的三种状态:待办、进行中和已完成。
// src/task.rs
// 任务状态枚举,使用derive宏自动实现Debug和PartialEq trait
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TaskStatus {
Todo, // 待办状态
InProgress, // 进行中状态
Completed, // 已完成状态
}
2. 设计 Task 结构体存储任务信息
在task.rs中继续定义Task结构体,包含任务 ID、标题、描述、截止日期和状态等字段,并为其实现构造函数和状态更新方法。
// src/task.rs
use std::string::String;
use std::time::SystemTime;
// 任务结构体,存储任务的详细信息
#[derive(Debug, Clone)]
pub struct Task {
pub id: u32, // 任务ID
pub title: String, // 任务标题
pub description: String, // 任务描述
pub due_date: SystemTime, // 截止日期
pub status: TaskStatus, // 任务状态
}
impl Task {
// 构造函数,创建新任务
pub fn new(id: u32, title: String, description: String, due_date: SystemTime) -> Self {
Task {
id,
title,
description,
due_date,
status: TaskStatus::Todo, // 默认状态为待办
}
}
// 更新任务状态的方法
pub fn update_status(&mut self, status: TaskStatus) {
self.status = status;
}
}
3. 在 main.rs 中测试 Task 结构体
修改src/main.rs文件,引入task.rs中定义的类型,并编写测试代码验证Task结构体的功能。
// src/main.rs
mod task; // 引入task模块
use task::{Task, TaskStatus};
use std::time::SystemTime;
fn main() {
println!("===== RustTask 数据模型测试 =====");
// 创建任务实例
let due_date = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
.unwrap().as_secs() + 86400; // 24小时后截止
let mut task = Task::new(
1,
"学习Rust".to_string(),
"完成《通过例子学Rust》前两章".to_string(),
SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(due_date),
);
println!("创建任务: {:?}", task);
// 更新任务状态
task.update_status(TaskStatus::InProgress);
println!("更新状态后: {:?}", task);
task.update_status(TaskStatus::Completed);
println!("任务完成: {:?}", task);
}
编译并运行程序,输出结果应类似:
===== RustTask 数据模型测试 =====
创建任务: Task { id: 1, title: "学习Rust", description: "完成《通过例子学Rust》前两章", due_date: 1687507200, status: Todo }
更新状态后: Task { id: 1, title: "学习Rust", description: "完成《通过例子学Rust》前两章", due_date: 1687507200, status: InProgress }
任务完成: Task { id: 1, title: "学习Rust", description: "完成《通过例子学Rust》前两章", due_date: 1687507200, status: Completed }
实践作业
扩展Task结构体,添加优先级字段(枚举类型)与相关操作方法,具体要求:
- 定义TaskPriority枚举,表示任务的优先级(如:低、中、高)
- 在Task结构体中添加priority字段
- 实现set_priority方法用于更新任务优先级
- 修改Task::new构造函数,添加优先级参数并设置默认值
- 在main.rs中测试新增的优先级功能
// 在此处编写代码
fn main() {
// 测试优先级功能
}
通过完成这个作业,你将进一步巩固枚举和结构体的定义与使用,理解如何通过自定义类型设计符合实际需求的数据模型。