结构体与枚举
这篇讲 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 的实现,让你能用 {:?} 打印结构体。常用的派生:Debug、Clone、PartialEq、Default。枚举(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 类型安全的基石。下一篇讲如何优雅地处理枚举——模式匹配。
练习
- 定义一个
Book结构体,包含title、author、pages字段。用impl为它添加一个summary(&self) -> String方法。 - 定义一个
TrafficLight枚举,包含Red、Yellow、Green三个变体。给每个变体关联一个 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());
}最后更新于