Rust教程05:结构体,枚举与模式匹配

第 1 章 结构体与方法

1.1 定义与实例化

结构体(struct)把相关的数据组合成一个自定义类型:

rust 复制代码
struct User {
    username: String,     // 字段: 类型
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("xiaoming"),
        active: true,
        sign_in_count: 1,       // 字段顺序无所谓
    };

    // 访问字段用点号
    println!("用户名:{}", user1.username);

    // 整个实例可变,才能改字段(Rust 不支持只改部分字段的可变性)
    let mut user2 = User {
        username: String::from("xiaohong"),
        email: String::from("hong@example.com"),
        active: false,
        sign_in_count: 0,
    };
    user2.email = String::from("new@example.com");
}

1.2 三个便捷语法

字段初始化简写

rust 复制代码
fn build_user(email: String, username: String) -> User {
    User {
        email,              // 等价于 email: email
        username,           // 变量名与字段名相同时可简写
        active: true,
        sign_in_count: 1,
    }
}

结构体更新语法

rust 复制代码
let user3 = User {
    email: String::from("another@example.com"),
    ..user1               // 其余字段从 user1 搬过来
};
// ⚠️ 注意:String 字段是移动,user1 中没显式列出的 String 字段会被移走
// 本例中 username、active、sign_in_count 来自 user1
// username 是 String → 移动后 user1.username 不能再用了

元组结构体与单元结构体

rust 复制代码
struct Color(i32, i32, i32);      // 元组结构体:有名字但没有字段名
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// black 和 origin 是不同类型,不能混用------这是它存在的意义

struct AlwaysEqual;               // 单元结构体:没有任何字段
let _subject = AlwaysEqual;       // 常用于实现 trait 的标记类型

1.3 一个演进示例:计算长方形面积

体会结构体如何让代码语义更清晰。

版本 1:散装的变量

rust 复制代码
fn area(width: u32, height: u32) -> u32 {
    width * height
}
// 问题:width 和 height 明明是一体的,却毫无关联

版本 2:元组

rust 复制代码
fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1   // 问题:.0 和 .1 是什么?读不懂
}

版本 3:结构体

rust 复制代码
struct Rectangle {
    width: u32,
    height: u32,
}

fn area(rect: &Rectangle) -> u32 {   // 借用,不夺取所有权
    rect.width * rect.height
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("面积 = {}", area(&rect));   // 1500
}

打印结构体:derive(Debug)

rust 复制代码
#[derive(Debug)]                 // 让编译器自动实现 Debug
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("{:?}", rect);     // Rectangle { width: 30, height: 50 }
    println!("{:#?}", rect);    // 美化版,每个字段一行
    dbg!(&rect);                // 调试宏:连文件名行号一起打印到 stderr
}

1.4 方法(method)

方法定义在 impl(implementation)块里,第一个参数是 self

rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // &self 是 self: &Self 的简写------借用实例
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 可以改自己:&mut self
    fn double(&mut self) {
        self.width *= 2;
        self.height *= 2;
    }

    // 可以吃掉自己:self(转移所有权,调用后实例失效)
    fn destroy(self) {
        println!("{:?} 被销毁", self);
    }

    // 判断能否容纳另一个长方形
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let mut rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };

    println!("面积 = {}", rect1.area());      // 1500,方法调用语法
    println!("能容纳吗?{}", rect1.can_hold(&rect2)); // true
    rect1.double();
    println!("翻倍后面积 = {}", rect1.area()); // 6000
}

self 的三种形态

  • &self:只读借用(最常用)
  • &mut self:可变借用(要修改字段)
  • self:拿走所有权(用于"转换/销毁"场景)

这依然遵守所有权规则------方法签名就把"这个方法对实例做什么"表达得清清楚楚。

1.5 关联函数(类似其他语言的"静态方法")

没有 self 参数的 impl 函数叫关联函数,用 :: 调用:

rust 复制代码
impl Rectangle {
    // 构造正方形------一种常见的"构造函数"惯例
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

fn main() {
    let sq = Rectangle::square(20);   // 用 :: 而不是 .
    println!("正方形面积 = {}", sq.area());  // 400
}

String::fromString::new 都是关联函数。一个结构体可以有多个 impl 块。

小结

  • struct 组合相关数据;实例整体可变或整体不可变
  • 便捷语法:字段简写、..base 更新语法、元组结构体
  • #[derive(Debug)] + {:?} / dbg! 打印结构体
  • impl 块里定义方法(&self / &mut self / self)和关联函数(Type::func()
  • 结构体存引用需要生命周期,暂用拥有所有权的类型

第 2 章 枚举与模式匹配

2.1 枚举:一个值是几种可能之一

rust 复制代码
enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

// 枚举可以做函数参数类型
fn route(ip_kind: IpAddrKind) { }

2.2 枚举变体可以携带数据(强大之处)

rust 复制代码
enum IpAddr {
    V4(u8, u8, u8, u8),      // 每个变体能带不同类型、不同数量的数据!
    V6(String),
}

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

一个更复杂的例子------消息枚举(替代多个结构体):

rust 复制代码
enum Message {
    Quit,                       // 无数据
    Move { x: i32, y: i32 },    // 像结构体一样的命名字段
    Write(String),              // 带一个 String
    ChangeColor(i32, i32, i32), // 带三个 i32
}

// 枚举也能有方法!
impl Message {
    fn call(&self) {
        println!("消息被处理了");
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

2.3 Option:Rust 没有 null

很多语言的 null 被称为"十亿美元的错误"。Rust 干脆没有 null,用标准库枚举 Option<T> 表达"可能有值、可能没有":

rust 复制代码
// 标准库里的定义(不用自己写):
// enum Option<T> {
//     Some(T),
//     None,
// }

let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;   // None 需要标注类型

关键点Option<i32>i32 是不同类型,不能直接运算------编译器强迫你先处理"可能没有值"的情况:

rust 复制代码
let x: i32 = 5;
let y: Option<i32> = Some(5);
// let sum = x + y;          // ❌ 编译错误!
let sum = x + y.unwrap_or(0); // ✅ 有值用值,没值用 0

常用方法:

rust 复制代码
let v = Some(42);
v.unwrap_or(0);      // 有值取值,无值用默认值 → 42
v.is_some();         // true
v.is_none();         // false
v.map(|n| n * 2);    // 有值则变换 → Some(84)
// v.unwrap();       // 有值取值,None 时 panic!(谨慎使用)

2.4 match:模式匹配

match 像一个超级 switch,但强大得多:

rust 复制代码
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("幸运币!");
            1                       // 分支可以是代码块,最后表达式是返回值
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

绑定值的模式

rust 复制代码
#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),   // Quarter 携带州名
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Quarter(state) => {     // state 绑定了枚举里的数据
            println!("来自 {:?} 的 25 美分!", state);
            25
        }
        _ => 1,                       // 简化示例
    }
}

match 必须穷尽所有可能

rust 复制代码
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),   // i 绑定 Some 内部的值
    }
}
// 如果漏掉 None 分支 → ❌ 编译错误:non-exhaustive patterns

编译器替你检查没有遗漏任何情况------重构时加一个枚举变体,所有没处理新情况的 match 都会报错,这是大型项目的安全保障。

_ 通配符

rust 复制代码
let dice = 9;
match dice {
    3 => println!("三"),
    7 => println!("七"),
    _ => println!("其他"),   // 匹配其余所有值
}

// 也可以匹配具体值范围、多值
match dice {
    1 | 2 => println!("小"),   // | 表示"或"
    3..=4 => println!("中"),   // 范围
    _ => println!("大"),
}

2.5 if let:只关心一种情况的简写

rust 复制代码
let some_value = Some(3u8);

// match 写法(啰嗦)
match some_value {
    Some(3) => println!("是三!"),
    _ => (),
}

// if let 写法(清爽)
if let Some(3) = some_value {
    println!("是三!");
}

// 还能带 else
if let Some(n) = some_value {
    println!("有值:{}", n);
} else {
    println!("没有值");
}

if let 本质上就是只处理一个分支的 match 的语法糖。会失去穷尽性检查,取舍由你。

2.6 综合示例:交通灯状态机

rust 复制代码
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    fn duration(&self) -> u32 {          // 各灯持续秒数
        match self {
            TrafficLight::Red => 60,
            TrafficLight::Yellow => 5,
            TrafficLight::Green => 45,
        }
    }

    fn next(&self) -> TrafficLight {     // 下一个状态
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Yellow => TrafficLight::Red,
            TrafficLight::Green => TrafficLight::Yellow,
        }
    }

    fn can_pass(&self) -> bool {
        match self {
            TrafficLight::Green => true,
            _ => false,
        }
    }
}

fn main() {
    let mut light = TrafficLight::Red;
    for _ in 0..6 {
        let name = match light {
            TrafficLight::Red => "红",
            TrafficLight::Yellow => "黄",
            TrafficLight::Green => "绿",
        };
        println!("{}灯,持续 {} 秒,{}通行",
                 name,
                 light.duration(),
                 if light.can_pass() { "可以" } else { "禁止" });
        light = light.next();
    }
}

输出:

小结

  • enum 表示"几种可能之一",每个变体可携带不同类型数据
  • Option<T>Some/None)取代 null,编译器强制处理"无值"情况
  • match 模式匹配,必须穷尽 ,可绑定值、用 _ 通配、| 多值、范围
  • if let / if let ... else 处理单一模式的简写
相关推荐
小小尚@3 小时前
AE脚本-AE Actions v1.1.8 操作动作记录器
开发语言·前端·javascript·jupyter·postman
程序员雷欧3 小时前
Java 反射深度解析:从原理到源码的全面剖析
java·开发语言·python
登登登__4 小时前
春秋招笔试题总结
java·开发语言
我的xiaodoujiao4 小时前
快速学习Python基础知识详细图文教程18--多线程
开发语言·python·学习·测试工具
想要入门的程序猿4 小时前
Qt插件QPluginLoader
开发语言·qt
GlobalInfo4 小时前
AI与另类数据融合,市场研究正在从“经验驱动”走向“数据智能”
大数据·网络·人工智能·ai
Rabitebla4 小时前
C++11 新特性详解(一):列表初始化、initializer_list 与右值引用
java·开发语言·数据结构·c++·算法·leetcode·list
天吾cc5 小时前
RTT-MQTT
网络·单片机·嵌入式硬件·算法
洋不写bug5 小时前
JavaComparable、Comparator、Cloneable接口解析,深拷贝浅拷贝详细解析
android·java·开发语言