Rust学习笔记:结构体与枚举——用Rust组织真正的业务数据

前几篇我们一直在和标量、字符串、函数打交道。实际业务中的数据远比这些复杂——用户信息、订单状态、配置项……这一篇学习 Rust 如何用结构体(struct)枚举(enum)来组织有意义的复合数据。

本文所有代码均已实测通过。


1. 结构体:把相关数据打包

1.1 定义与实例化

struct User {
    username: String,
    email: String,
    active: bool,
}

let user = User {
    username: String::from("alice"),
    email: String::from("alice@example.com"),
    active: true,
};

// 访问字段
println!("{} <{}>", user.username, user.email);

和 C 的结构体很像,但 Rust 的 struct 是真正的类型,可以直接持有 String 而不用操心内存。

修改字段需要整个实例是 mut 的——Rust 没有「部分字段可变」:

let mut user2 = User {
    username: String::from("bob"),
    email: String::from("bob@example.com"),
    active: false,
};
user2.active = true; // ✅ 整个 user2 是 mut

1.2 结构体所有权的注意事项

上面的 User 用了 String 而不是 &str,这意味着结构体自己拥有所有数据。如果用引用(&str),就需要标注生命周期——初学者先避开,就用 String


2. impl:给结构体添加方法

impl 块是把函数「挂」到类型上的方式:

impl User {
    // 关联函数(没有 self)——相当于静态方法、构造函数
    fn new(username: &str, email: &str) -> User {
        User {
            username: String::from(username),
            email: String::from(email),
            active: true,
        }
    }

    // 方法(&self)——只读访问
    fn summary(&self) -> String {
        format!("{} <{}>", self.username, self.email)
    }

    // 可变方法(&mut self)——修改自身
    fn deactivate(&mut self) {
        self.active = false;
    }
}

let mut user = User::new("charlie", "charlie@example.com");
println!("{}", user.summary()); // charlie <charlie@example.com>
user.deactivate();

关键区分:

参数 含义 能否修改字段
&self 不可变借用,只读
&mut self 可变借用,可修改
self 拿走所有权,消耗掉实例 ✅(用完就没了)

Rust 没有「类」的概念,struct + impl 就是它的面向对象。数据和行为分开声明,但逻辑上紧密结合。


3. 元组结构体:给元组起个名

有时候不需要给字段取名字,只想给一个元组赋予类型含义:

struct Color(i32, i32, i32);
struct Point(f64, f64, f64);

let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0, 0.0);

println!("黑色: ({}, {}, {})", black.0, black.1, black.2);

注意 ColorPoint 虽然内部结构相同(都是三个数值),但它们是完全不同的类型——不能把 Color 传给接受 Point 的函数。这就是元组结构体的价值:用类型系统表达语义,避免混淆。


4. 枚举:一个值可以是多种可能

Rust 的枚举比大多数语言强大得多——每个变体可以携带不同类型、不同数量的数据。

enum IpAddr {
    V4(u8, u8, u8, u8),   // 带四个 u8
    V6(String),             // 带一个 String
}

let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));

更复杂的例子——每个变体都可以有完全不同的数据形态:

enum Message {
    Quit,                       // 无数据
    Move { x: i32, y: i32 },    // 命名字段
    Write(String),              // 元组风格
    ChangeColor(i32, i32, i32), // 元组风格
}

结合 impl,枚举也可以有方法:

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("退出"),
            Message::Move { x, y } => println!("移动到 ({x}, {y})"),
            Message::Write(text) => println!("写入: {text}"),
            Message::ChangeColor(r, g, b) => println!("改色: ({r}, {g}, {b})"),
        }
    }
}

这个设计在模式匹配中威力巨大——编译器不仅检查穷尽性,还能通过模式解构直接提取每个变体携带的数据。


5. Option<T>:Rust 没有 null

Rust 没有 null。它用 Option<T> 枚举来表达「可能有值,也可能没有」:

enum Option<T> {
    None,
    Some(T),
}

Option<T> 是标准库预置的,不用自己定义,直接使用:

let some_number = Some(42);
let absent: Option<i32> = None;

解包 Option

match 解包:

match some_number {
    Some(n) => println!("有值: {n}"),
    None => println!("无值"),
}

if let 简化写法——只关心一个分支时:

if let Some(n) = some_number {
    println!("解包: {n}");
}

快捷方法:

some_number.unwrap();          // 直接取值,None 会 panic(不安全!)
absent.unwrap_or(0);           // 取值,None 时给默认值 0

生产代码中尽量用 matchunwrap_or 处理 Option,裸 unwrap() 只适合快速原型。


总结

这一篇覆盖了 Rust 组织业务数据的核心工具:

  1. 结构体——打包相关字段,impl 挂方法,关联函数做构造函数
  2. 元组结构体——给元组赋予类型语义,ColorPoint 不能互换
  3. 枚举——每个变体可以带不同数据,配合 match 穷尽解构
  4. Option<T>——替代 null,编译器强制你处理「无值」的情况

结构体 + 枚举 + match 构成了 Rust 类型系统的表达核心。掌握这三样,大部分业务逻辑都能用类型安全的方式表达清楚。

下一篇进入集合类型Vec(动态数组)和 HashMap(键值对),让数据真正「活」起来。

posted @ 2026-09-13 14:20  PC2005-cloud  阅读(6)  评论(0)    收藏  举报