结构体与枚举
结构体(struct)和枚举(enum)是 Rust 中定义自定义类型的两种主要方式。它们让你能够创建有意义的数据抽象。
结构体(Struct)
Section titled “结构体(Struct)”结构体用于将相关的数据组合在一起。
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");}字段初始化简写
Section titled “字段初始化简写”当变量名与字段名相同时,可以简写:
fn build_user(email: String, username: String) -> User { User { email, // 等同于 email: email username, // 等同于 username: username active: true, sign_in_count: 1, }}结构体更新语法
Section titled “结构体更新语法”基于现有实例创建新实例:
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;}注意:Color 和 Point 是不同的类型,即使它们的结构相同。
没有任何字段的结构体:
struct AlwaysEqual;
fn main() { let subject = AlwaysEqual;}单元结构体常用于实现 trait 而不需要存储数据。
方法与关联函数
Section titled “方法与关联函数”使用 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 的三种形式
Section titled “self 的三种形式”| 形式 | 含义 |
|---|---|
&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 块
Section titled “多个 impl 块”可以有多个 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)
Section titled “枚举(Enum)”枚举允许你定义一个类型,它可以是几种不同变体中的一种。
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"));}不同变体不同数据
Section titled “不同变体不同数据”enum Message { Quit, // 没有数据 Move { x: i32, y: i32 }, // 匿名结构体 Write(String), // 包含 String ChangeColor(i32, i32, i32), // 包含三个 i32}为枚举定义方法
Section titled “为枚举定义方法”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 } }}匹配 Option
Section titled “匹配 Option”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!("其他"), // 处理所有其他情况 }}if let 简洁匹配
Section titled “if let 简洁匹配”当只关心一种情况时:
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!("没有值"); }}常用的派生 Trait
Section titled “常用的派生 Trait”使用 #[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)PartialEq、Eq:相等比较PartialOrd、Ord:排序比较Default:默认值Hash:哈希计算
练习 1:定义 Rectangle
Section titled “练习 1:定义 Rectangle”定义一个 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));}练习 2:创建正方形
Section titled “练习 2:创建正方形”为 Rectangle 实现关联函数 square(size) 创建正方形:
impl Rectangle { fn square(size: u32) -> Rectangle { // 你的代码 }}
fn main() { let sq = Rectangle::square(10); println!("正方形面积: {}", sq.area());}练习 3:定义 Message 枚举
Section titled “练习 3:定义 Message 枚举”定义一个 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(); }}练习 4:使用派生宏
Section titled “练习 4:使用派生宏”为以下结构体派生 Debug、Clone 和 PartialEq,并测试这些功能:
// 添加派生宏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 比较}- The Rust Book - 结构体
- The Rust Book - 枚举
- The Rust Book - 方法语法
- Rust By Example - 结构体
- Rust By Example - 枚举
掌握了结构体和枚举后,下一章我们将学习 Rust 的错误处理机制。