跳至内容

结构体与枚举

这篇讲 Rust 的两种核心自定义类型:结构体(struct)和枚举(enum)。结构体让你组织数据,枚举让你建模多种可能——两者配合是 Rust 表达力的基础。

结构体(Struct)

定义与实例化

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

fn main() {
    let user1 = User {
        email: String::from("[email protected]"),
        username: String::from("alice"),
        active: true,
        sign_in_count: 1,
    };

    // 访问字段
    println!("{}", user1.email);
}

字段初始化简写

当变量名和字段名相同时,可以省略冒号:

fn build_user(email: String, username: String) -> User {
    User {
        email,       // email: email 的简写
        username,    // username: username 的简写
        active: true,
        sign_in_count: 1,
    }
}

结构体更新语法

从已有实例创建新实例——类似 ES6 的 spread:

let user2 = User {
    email: String::from("[email protected]"),
    ..user1  // 其余字段从 user1 复制
};
// 注意:user1.username 被 move 给 user2,user1 不再可用
// 但 user1.active 和 user1.sign_in_count 是 i32 和 bool(Copy 类型),它们不受影响

元组结构体

一种带名称的元组——有结构体名但没有字段名:

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

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    // Color 和 Point 是不同的类型,即使内部结构相同也不能混用
}

单元结构体

没有任何字段的结构体,用作标记或 trait 实现:

struct AlwaysEqual;

fn main() {
    let _subject = AlwaysEqual;
}

方法与关联函数

impl 块为结构体添加方法:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 方法:第一个参数是 &self(或 &mut self、self)
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width >= other.width && self.height >= other.height
    }

    // 关联函数:没有 self 参数(类似静态方法)
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}

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

    let square = Rectangle::square(20);                    // 调用关联函数
    println!("正方形能容纳矩形吗?{}", square.can_hold(&rect));  // false
}
#[derive(Debug)] 是 Rust 的派生宏——放在结构体上自动生成 Debug trait 的实现,让你能用 {:?} 打印结构体。常用的派生:DebugClonePartialEqDefault

枚举(Enum)

枚举让你定义一个类型的所有可能取值,每个取值可以携带不同类型的数据——这是代数数据类型(ADT)的核心:

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

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

Rust 的枚举比 C/Go 的枚举强大得多——每个变体可以携带不同的数据类型和数量。这是模式匹配能如此强大的原因。

一个经典的枚举:Option

Rust 没有 null。对于「值可能存在也可能不存在」的场景,使用 Option<T> 枚举:

enum Option<T> {
    None,       // 没有值
    Some(T),    // 有值,值是 T 类型
}

// Option 是 prelude 的一部分,可以直接使用
fn main() {
    let some_number = Some(5);
    let some_string = Some("hello");
    let absent: Option<i32> = None;  // 必须标注类型

    // 不能直接运算——需要先解包
    // let sum = some_number + 1;    // ❌ Option<i32> 不能直接和 i32 相加
}
没有 null 是 Rust 最出色的设计决策之一。Tony Hoare(null 的发明者)称 null 为「十亿美元的错误」。Rust 用 Option<T> 迫使你在编译期就处理「值可能不存在」的情况——你不可能忘记 null 检查,因为编译器不让你跳过。

Result 枚举

Result<T, E> 用于可能失败的操作——处理错误的标准方式:

enum Result<T, E> {
    Ok(T),    // 成功,携带返回值
    Err(E),   // 失败,携带错误信息
}

Result 的详细用法将在 错误处理 中深入学习。

枚举的方法

和结构体一样,枚举也能用 impl 定义方法:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        match self {
            Message::Write(s) => println!("写入: {s}"),
            _ => println!("其他消息"),
        }
    }
}

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

一句话小结

结构体组织数据(类似 Go 的 struct、C++ 的 class 去掉继承),枚举建模可能取值(比其它语言的 enum 强大得多)。Option<T> 替代 null,Result<T, E> 替代异常——这是 Rust 类型安全的基石。下一篇讲如何优雅地处理枚举——模式匹配

练习

  1. 定义一个 Book 结构体,包含 titleauthorpages 字段。用 impl 为它添加一个 summary(&self) -> String 方法。
  2. 定义一个 TrafficLight 枚举,包含 RedYellowGreen 三个变体。给每个变体关联一个 u8 值表示持续时间(秒)。
参考答案
#[derive(Debug)]
struct Book {
    title: String,
    author: String,
    pages: u32,
}

impl Book {
    fn summary(&self) -> String {
        format!("《{}》 - {},共 {} 页", self.title, self.author, self.pages)
    }
}

enum TrafficLight {
    Red(u8),
    Yellow(u8),
    Green(u8),
}

impl TrafficLight {
    fn duration(&self) -> u8 {
        match self {
            TrafficLight::Red(d) => *d,
            TrafficLight::Yellow(d) => *d,
            TrafficLight::Green(d) => *d,
        }
    }
}

fn main() {
    let book = Book {
        title: String::from("Rust 程序设计"),
        author: String::from("Klabnik & Nichols"),
        pages: 560,
    };
    println!("{}", book.summary());

    let red = TrafficLight::Red(60);
    println!("红灯持续 {} 秒", red.duration());
}
最后更新于