CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Rust 语言入门/课时/结构体与枚举
RawBlame
课时目录12 节

Rust 语言入门

  1. 01Rust 是什么
  2. 02基础语法
  3. 03所有权与借用
  4. 04生命周期
  5. 05结构体与枚举
  6. 06错误处理
  7. 07Trait 与泛型
  8. 08集合类型
  9. 09并发与 async
  10. 10宏与元编程
  11. 11Web 与生态
  12. 12综合练习

结构体与枚举

struct、enum、pattern matching、impl blocks。

35 分钟

结构体与枚举

这一课你将能做到

  1. 用 struct 建模数据(含方法与关联函数),用 enum 建模「多态状态」
  2. 熟练使用 match 做穷尽式模式匹配,含解构与绑定
  3. 用 Option 处理「可能没有」的值,掌握 if let 与 let else

为什么要学

Rust 没有 class 和继承,数据建模靠两个原语:struct 管数据形状,enum 管「可能是这几种情况之一」。组合起来能表达 Java 里要靠继承树或 sealed interface 才能表达的东西,而且 match 的穷尽性检查保证新增状态时所有处理点都被编译器揪出来。

判题系统的核心状态机——AC/WA/TLE/RE——就是 enum 的教科书场景。Java 21 的 record + sealed + switch 模式匹配正在向 Rust 靠拢,学完这课你对两种语言的理解都会加深。

先建立图像

// struct:值的形状(Java 的 record/POJO)
struct Submission {
    id: u64,
    language: String,
}

// enum:可能的状态集合,每个变体可以携带不同的数据(Java enum + sealed interface 合体)
enum Verdict {
    Accepted { time_ms: u32 },           // 结构体风格变体
    WrongAnswer(u32, String),            // 元组风格:样例号 + 差异信息
    CompileError(String),                // 单值
    TimeLimit,                           // 无数据
}

match 时编译器强制四种变体全部处理,漏一种就编译失败——这是 Java 的 default 分支给不了的安全性。

跟着做

步骤 1:定义结构体、实现方法

cargo new type-demo
cd type-demo
#[derive(Debug, Clone)]        // derive 自动实现调试打印与克隆,第 10 课展开
struct Submission {
    id: u64,
    language: String,
    time_used_ms: u32,
    memory_kb: u32,
}

impl Submission {
    // 关联函数:没有 self,对标 Java 静态工厂方法。惯例命名 new
    fn new(id: u64, language: &str) -> Self {
        Self { id, language: language.to_string(), time_used_ms: 0, memory_kb: 0 }
    }

    // &self:只读方法
    fn is_fast(&self) -> bool {
        self.time_used_ms < 1000
    }

    // &mut self:可修改自身
    fn record(&mut self, time_ms: u32, mem_kb: u32) {
        self.time_used_ms = time_ms;
        self.memory_kb = mem_kb;
    }
}

fn main() {
    let mut sub = Submission::new(1, "cpp");
    sub.record(128, 4096);
    println!("{sub:?}");     // {:?} 走 Debug derive
    println!("达标:{}", sub.is_fast());
}

三种 self:self 拿走所有权(少用)、&self 只读借用(默认选择)、&mut self 可变借用。

步骤 2:枚举携带数据

#[derive(Debug, Clone, PartialEq)]
enum Verdict {
    Accepted { time_ms: u32 },
    WrongAnswer(u32, String),
    CompileError(String),
    TimeLimit,
}

fn main() {
    let results = vec![
        Verdict::Accepted { time_ms: 128 },
        Verdict::WrongAnswer(3, "expected 42, got 41".into()),
        Verdict::CompileError("undefined reference".into()),
        Verdict::TimeLimit,
    ];
    for v in &results {
        println!("{v:?}");
    }
}

注意每个变体的数据形状都不同——Java 的 enum 只能挂固定字段,Rust 的 enum 是「和类型」(tagged union),表达力对标 sealed interface 的整个继承树。

步骤 3:match 模式匹配

fn describe(v: &Verdict) -> String {
    match v {
        Verdict::Accepted { time_ms } => format!("AC,耗时 {time_ms} ms"),
        Verdict::WrongAnswer(case_id, diff) => format!("WA,样例 #{case_id}:{diff}"),
        Verdict::CompileError(msg) => format!("CE:{msg}"),
        Verdict::TimeLimit => "TLE".to_string(),
    }
}

四个要点:解构直接把变体内部数据拆成局部变量;不需要类型转换与 instanceof;少一个分支编译报错(穷尽性);_ 通配兜底(但核心业务枚举建议显式列全,让新增变体强制暴露)。

步骤 4:Option——用类型表达「可能没有」

fn find(store: &[Submission], id: u64) -> Option<&Submission> {
    store.iter().find(|s| s.id == id)
}

fn main() {
    // 沿用步骤 1 的 Submission,这里补两行构造测试数据
    let mut store = vec![Submission::new(1, "cpp"), Submission::new(2, "java")];
    store[0].record(88, 2048);

    // if let:只关心命中一种情况
    if let Some(sub) = find(&store, 1) {
        println!("找到 {},耗时 {} ms", sub.id, sub.time_used_ms);
    }

    // let else:守卫主流程(Rust 1.65+)
    let Some(target) = find(&store, 42) else {
        eprintln!("提交 42 不存在");
        return;
    };
    println!("{target:?}");
}

Option<T> 是 enum:Some(T) 或 None。编译器强制你处理 None——Java 的 NullPointerException 在 Rust 里是编译错误。

步骤 5:把两者拼成完整状态机

fn judge(code: &str) -> Verdict {
    if code.contains("fn main") {
        if code.contains("BUG") { Verdict::WrongAnswer(1, "output mismatch".into()) }
        else { Verdict::Accepted { time_ms: 96 } }
    } else {
        Verdict::CompileError("no entry point".into())
    }
}

fn main() {
    for code in ["fn main() {}", "fn main() { BUG }", "print(1)"] {
        println!("{:?} => {}", code, describe(&judge(code)));
    }
}

讲清楚要点

  1. struct 是积木,enum 是分叉:数据用 struct,状态机/多分支结果用 enum;Rust 的多态首选 enum + match,而非 trait 继承。
  2. match 是表达式:分支值直接作为结果,所有分支必须同类型;穷尽性检查让「新增枚举变体后漏改的 if-else」这类事故在编译期暴露。
  3. 关联函数 vs 方法:没有 self 的是关联函数(对标静态方法,惯用 new);按 &self / &mut self / self 决定对实例的访问级别。
  4. Option 消灭 null:可能缺失的值类型上就是 Option<T>,配合 ?(第 6 课)、if let、unwrap_or 形成完整工具链。
  5. derive 省样板:#[derive(Debug, Clone, PartialEq)] 一行顶 Java 手写的 toString/equals/clone 骨架;自定义行为用 impl(第 7、10 课展开)。

你可能会懵的地方

  • 没有继承,怎么复用代码? 组合 + trait(第 7 课)。数据复用直接把 struct 作为字段嵌进去;行为复用定义 trait 再实现。继承的「脆弱基类问题」在 Rust 里从根上不存在。

  • enum 变体的数据怎么「共享部分字段」? 变体之间不共享字段,这是刻意的:把公共字段提到外层 struct(如 struct Result { submission_id: u64, verdict: Verdict }),比 Java 继承基类更扁平。

  • match 和 Java switch 的区别? match 可解构任意嵌套的模式、可加绑定、可带守卫 if 条件,且强制穷尽;Java 21 的模式匹配 switch 是向它学习的产物,但默认非穷尽。

  • Option<T> 性能上有开销吗? 没有。编译器对小类型做了优化(niche optimization),Option<&T> 与裸指针同大小,判空就是判指针,零成本。

  • 什么时候用 unwrap 是合理的? 测试代码、原型阶段、以及你能证明 None 不可能出现的场景(并留注释)。生产代码用 ?、unwrap_or、expect("原因") 把缺失路径说清楚。

错法对照

错法问题正解
struct 字段没全部初始化error[E0063] 缺字段补齐字段,或用 ..Default::default()(需 derive Default)
match 漏掉某个变体error[E0004] 不穷尽补分支;确属不关心的用 _ 显式兜底
if v == Some(x) 直接和裸值比较类型不同无法比较if let Some(x) = v 或 matches!(v, Some(_))
方法签名漏写 selferror: 不是合法方法方法第一个参数必须是 self/&self/&mut self
把 Option<T> 当 T 用(如直接算术运算)error[E0369]先解包:? / if let / unwrap_or

卡点急救

  1. error[E0004] non-exhaustive patterns:缺分支。报错信息会精确列出漏掉的变体名;补上或写 _ =>(但优先显式列全)。
  2. error[E0308] 分支类型不一致:match 是表达式,所有分支必须同类型;常见坑是某分支漏了 .to_string() 返回了 &str。
  3. error[E0596] cannot borrow as mutable:实例不是 mut 声明却调用了 &mut self 方法,把绑定改成 let mut。

面试常问

  • Rust 的 enum 和 Java 的 enum 有什么本质区别? Rust enum 是代数数据类型(sum type),每个变体携带任意异构数据,等价于 sealed interface + 多个 record;Java enum 变体共享固定字段。表达力上 Rust enum 更强,match 穷尽性检查把它变成类型安全的状态机。

  • Option<T> 为什么比 null 好? null 混进了任何引用类型,编译器无法区分「可能为空」和「绝不为空」;Option 把可能性写进类型,强制处理 None 分支,NPE 这类运行时崩溃被编译期拦截,且零运行时开销。

  • match 的穷尽性在工程上有什么价值? 状态机重构时(新增 RE 变体、合并 WA 分支),所有未更新的处理点全部编译报错,重构安全性由编译器保证,不依赖 grep 和人肉 review。

自测三问

  1. &self、&mut self、self 三种方法签名分别适用什么场景?
  2. 写一个 match 分支,同时解构 Verdict::Accepted { time_ms } 并只对 time_ms > 1000 输出警告(提示:match 守卫)。
  3. if let、let else、unwrap_or 各适合处理 Option 的哪种情况?

小结一句话

struct 定形状、enum 定状态、match 强制穷尽、Option 驱逐 null——Rust 用四个原语完成了 Java 需要 class、enum、sealed、Optional 加继承体系才能拼出的建模能力。

延伸

  • The Rust Book ch05+ch06:结构体与枚举
  • 下一课:错误处理——panic 与 Result 的分工,? 运算符让错误传播一行搞定。
加载本节练习…
← 上一课生命周期下一课 →错误处理