第二章:数据模型设计与自定义类型

62 阅读7分钟

第二章:数据模型设计与自定义类型

教学目标

  • 掌握结构体(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结构体,添加优先级字段(枚举类型)与相关操作方法,具体要求:

  1. 定义TaskPriority枚举,表示任务的优先级(如:低、中、高)
  1. 在Task结构体中添加priority字段
  1. 实现set_priority方法用于更新任务优先级
  1. 修改Task::new构造函数,添加优先级参数并设置默认值
  1. main.rs中测试新增的优先级功能
// 在此处编写代码
fn main() {
    // 测试优先级功能
}

通过完成这个作业,你将进一步巩固枚举和结构体的定义与使用,理解如何通过自定义类型设计符合实际需求的数据模型。