Skip to content

结构体与枚举

结构体(struct)和枚举(enum)是 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("someusername"),
active: true,
sign_in_count: 1,
};
// 访问字段
println!("用户名: {}", user1.username);
}

整个实例必须是可变的,不能只让某些字段可变:

fn main() {
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername"),
active: true,
sign_in_count: 1,
};
// 修改字段
user1.email = String::from("anotheremail@example.com");
}

当变量名与字段名相同时,可以简写:

fn build_user(email: String, username: String) -> User {
User {
email, // 等同于 email: email
username, // 等同于 username: username
active: true,
sign_in_count: 1,
}
}

基于现有实例创建新实例:

fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername"),
active: true,
sign_in_count: 1,
};
// 使用 user1 的部分值创建 user2
let user2 = User {
email: String::from("another@example.com"),
..user1 // 其余字段从 user1 获取
};
// 注意:user1.username 已被移动,user1.username 不能再使用
// 但 user1.active 和 user1.sign_in_count 仍可使用(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);
// 访问元素
println!("R: {}", black.0);
// 解构
let Color(r, g, b) = black;
}

注意:ColorPoint 是不同的类型,即使它们的结构相同。

没有任何字段的结构体:

struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}

单元结构体常用于实现 trait 而不需要存储数据。

使用 impl 块为结构体定义方法:

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 方法:第一个参数是 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 double(&mut self) {
self.width *= 2;
self.height *= 2;
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
println!("面积: {}", rect1.area());
println!("rect1 能容纳 rect2 吗?{}", rect1.can_hold(&rect2));
}
形式含义
&self不可变借用(最常用)
&mut self可变借用
self获取所有权(少见)

不以 self 为第一个参数的函数:

impl Rectangle {
// 关联函数,常用作构造器
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
}
fn main() {
// 使用 :: 调用关联函数
let sq = Rectangle::square(30);
let rect = Rectangle::new(10, 20);
}

可以有多个 impl 块:

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}

枚举允许你定义一个类型,它可以是几种不同变体中的一种。

enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
}

枚举变体可以携带数据:

enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
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), // 包含 String
ChangeColor(i32, i32, i32), // 包含三个 i32
}
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),
}
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}

match 是处理枚举的强大工具。

enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(String), // 带有州名
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("来自 {} 州的 25 美分!", state);
25
}
}
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}

match 必须处理所有可能的情况:

fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
// 错误!没有处理 None
}
}

使用 _ 通配符处理其余情况:

fn main() {
let x = 5;
match x {
1 => println!(""),
2 => println!(""),
3 => println!(""),
_ => println!("其他"), // 处理所有其他情况
}
}

当只关心一种情况时:

fn main() {
let some_value = Some(3);
// 使用 match
match some_value {
Some(3) => println!("三!"),
_ => (),
}
// 使用 if let(更简洁)
if let Some(3) = some_value {
println!("三!");
}
// if let ... else
if let Some(n) = some_value {
println!("值是: {}", n);
} else {
println!("没有值");
}
}

使用 #[derive()] 自动实现常用 trait:

#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
// Debug:格式化输出
println!("{:?}", p1);
println!("{:#?}", p1); // 美化输出
// Clone:复制
let p2 = p1.clone();
// PartialEq:比较
println!("p1 == p2: {}", p1 == p2);
}

常用的可派生 trait:

  • Debug:格式化调试输出
  • Clone:深拷贝
  • Copy:按位复制(要求所有字段都是 Copy)
  • PartialEqEq:相等比较
  • PartialOrdOrd:排序比较
  • Default:默认值
  • Hash:哈希计算

定义一个 Rectangle 结构体,实现 area()can_hold() 方法:

struct Rectangle {
// 你的代码
}
impl Rectangle {
fn area(&self) -> u32 {
// 你的代码
}
fn can_hold(&self, other: &Rectangle) -> bool {
// 你的代码
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
println!("面积: {}", rect1.area());
println!("rect1 能容纳 rect2: {}", rect1.can_hold(&rect2));
}

Rectangle 实现关联函数 square(size) 创建正方形:

impl Rectangle {
fn square(size: u32) -> Rectangle {
// 你的代码
}
}
fn main() {
let sq = Rectangle::square(10);
println!("正方形面积: {}", sq.area());
}

定义一个 Message 枚举,包含以下变体:

  • Quit:无数据
  • Move { x: i32, y: i32 }:包含坐标
  • Write(String):包含消息文本
  • ChangeColor(i32, i32, i32):包含 RGB 值

为其实现 call() 方法,根据不同变体打印不同信息:

enum Message {
// 你的代码
}
impl Message {
fn call(&self) {
// 你的代码
}
}
fn main() {
let messages = vec![
Message::Quit,
Message::Move { x: 10, y: 20 },
Message::Write(String::from("hello")),
Message::ChangeColor(255, 0, 0),
];
for m in messages {
m.call();
}
}

为以下结构体派生 DebugClonePartialEq,并测试这些功能:

// 添加派生宏
struct Book {
title: String,
author: String,
pages: u32,
}
fn main() {
let book1 = Book {
title: String::from("Rust 编程"),
author: String::from("作者"),
pages: 500,
};
// 使用 Debug 打印
// 使用 Clone 复制
// 使用 PartialEq 比较
}

掌握了结构体和枚举后,下一章我们将学习 Rust 的错误处理机制。