结构体(Struct)
结构体(Struct)是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。
定义和实例化结构体
结构体的定义需要使用 struct
关键字并为整个结构体提供一个名字:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
println!("用户名: {}", user1.username);
}
访问结构体字段
要从结构体中获取某个特定的值,可以使用点号:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
println!("新邮箱: {}", user1.email);
}
注意整个实例必须是可变的;Rust 并不允许只将某个字段标记为可变。
使用字段初始化简写语法
当变量与字段同名时,可以使用字段初始化简写语法(field init shorthand):
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
User {
active: true,
username, // 简写语法
email, // 简写语法
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
println!("用户: {}", user1.username);
}
使用结构体更新语法从其他实例创建实例
使用结构体更新语法(struct update syntax)可以使用另一个实例的大部分值但改变其部分值来创建一个新的结构体实例:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1 // 使用 user1 的其余字段
};
// 注意:user1 不再有效,因为 username 被移动到了 user2
// println!("{}", user1.username); // 这会编译错误
println!("用户2的邮箱: {}", user2.email);
}
元组结构体
也可以定义与元组类似的结构体,称为元组结构体(tuple structs):
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!("黑色: ({}, {}, {})", black.0, black.1, black.2);
println!("原点: ({}, {}, {})", origin.0, origin.1, origin.2);
}
类单元结构体
我们也可以定义一个没有任何字段的结构体!它们被称为类单元结构体(unit-like structs):
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
// 类单元结构体常用于实现 trait 但不需要存储数据的情况
}
什么是 Trait?
在深入了解类单元结构体的用途之前,我们需要理解 trait 的概念。
Trait 是 Rust 中定义共享行为的方式,类似于其他语言中的接口(interface)。它定义了类型必须实现的方法签名。
💡 详细学习 Trait:关于 trait 的完整介绍和基础用法,请参考 Trait(特征)基础 章节。这里只是简单介绍 trait 与类单元结构体的结合使用。
Trait 的基本概念
// 定义一个 trait
trait Drawable {
fn draw(&self);
// 可以有默认实现
fn description(&self) -> &str {
"这是一个可绘制的对象"
}
}
// 为结构体实现 trait
struct Circle {
radius: f64,
}
impl Drawable for Circle {
fn draw(&self) {
println!("绘制一个半径为 {} 的圆", self.radius);
}
}
fn main() {
let circle = Circle { radius: 5.0 };
circle.draw();
println!("{}", circle.description());
}
类单元结构体与 Trait 的结合
类单元结构体经常用于实现 trait,特别是当你需要一个类型来承载行为,但不需要存储任何数据时:
// 定义一个处理器 trait
trait Processor {
fn process(&self, data: &str) -> String;
}
// 类单元结构体 - 不存储数据,只提供行为
struct UpperCaseProcessor;
struct LowerCaseProcessor;
struct ReverseProcessor;
// 为类单元结构体实现 trait
impl Processor for UpperCaseProcessor {
fn process(&self, data: &str) -> String {
data.to_uppercase()
}
}
impl Processor for LowerCaseProcessor {
fn process(&self, data: &str) -> String {
data.to_lowercase()
}
}
impl Processor for ReverseProcessor {
fn process(&self, data: &str) -> String {
data.chars().rev().collect()
}
}
fn main() {
let text = "Hello World";
let upper = UpperCaseProcessor;
let lower = LowerCaseProcessor;
let reverse = ReverseProcessor;
println!("原文: {}", text);
println!("大写: {}", upper.process(text));
println!("小写: {}", lower.process(text));
println!("反转: {}", reverse.process(text));
}
实际应用场景
1. 标记 Trait(Marker Traits)
// 标记 trait - 不包含任何方法,只用于标记类型的特性
trait Safe {}
trait Unsafe {}
struct SafeOperation;
struct UnsafeOperation;
// 实现标记 trait
impl Safe for SafeOperation {}
impl Unsafe for UnsafeOperation {}
// 使用泛型约束
fn execute_safe_operation<T: Safe>(op: T) {
println!("执行安全操作");
}
fn main() {
let safe_op = SafeOperation;
execute_safe_operation(safe_op); // ✅ 编译通过
// let unsafe_op = UnsafeOperation;
// execute_safe_operation(unsafe_op); // ❌ 编译错误
}
2. 策略模式实现
trait SortStrategy {
fn sort(&self, data: &mut Vec<i32>);
}
struct BubbleSort;
struct QuickSort;
struct MergeSort;
impl SortStrategy for BubbleSort {
fn sort(&self, data: &mut Vec<i32>) {
println!("使用冒泡排序");
// 冒泡排序实现(简化)
data.sort();
}
}
impl SortStrategy for QuickSort {
fn sort(&self, data: &mut Vec<i32>) {
println!("使用快速排序");
// 快速排序实现(简化)
data.sort();
}
}
impl SortStrategy for MergeSort {
fn sort(&self, data: &mut Vec<i32>) {
println!("使用归并排序");
// 归并排序实现(简化)
data.sort();
}
}
struct Sorter<T: SortStrategy> {
strategy: T,
}
impl<T: SortStrategy> Sorter<T> {
fn new(strategy: T) -> Self {
Self { strategy }
}
fn sort_data(&self, data: &mut Vec<i32>) {
self.strategy.sort(data);
}
}
fn main() {
let mut numbers = vec![64, 34, 25, 12, 22, 11, 90];
let bubble_sorter = Sorter::new(BubbleSort);
bubble_sorter.sort_data(&mut numbers.clone());
let quick_sorter = Sorter::new(QuickSort);
quick_sorter.sort_data(&mut numbers.clone());
let merge_sorter = Sorter::new(MergeSort);
merge_sorter.sort_data(&mut numbers);
}
3. 单例模式和全局状态
trait Logger {
fn log(&self, message: &str);
}
struct ConsoleLogger;
struct FileLogger;
impl Logger for ConsoleLogger {
fn log(&self, message: &str) {
println!("[LOG] {}", message);
}
}
impl Logger for FileLogger {
fn log(&self, message: &str) {
// 实际项目中会写入文件
println!("[FILE] {}", message);
}
}
// 全局日志函数
fn log_with<T: Logger>(logger: &T, message: &str) {
logger.log(message);
}
fn main() {
let console = ConsoleLogger;
let file = FileLogger;
log_with(&console, "这是控制台日志");
log_with(&file, "这是文件日志");
}
常见的标准库 Trait
// Debug trait - 用于调试输出
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
// Clone trait - 用于克隆
#[derive(Clone)]
struct Data {
value: String,
}
// PartialEq trait - 用于相等比较
#[derive(PartialEq)]
struct ID {
number: u32,
}
fn main() {
let point = Point { x: 1, y: 2 };
println!("{:?}", point); // Debug trait
let data = Data { value: String::from("hello") };
let cloned = data.clone(); // Clone trait
let id1 = ID { number: 123 };
let id2 = ID { number: 123 };
println!("相等吗?{}", id1 == id2); // PartialEq trait
}
为什么类单元结构体适合实现 Trait?
- 零成本抽象:不占用内存空间
- 类型安全:每个处理器都是不同的类型
- 清晰的语义:代码意图明确
- 编译时优化:编译器可以内联优化
use std::mem;
struct DataProcessor;
struct EmptyStruct {}
struct UnitStruct;
fn main() {
println!("DataProcessor 大小: {} 字节", mem::size_of::<DataProcessor>());
println!("EmptyStruct 大小: {} 字节", mem::size_of::<EmptyStruct>());
println!("UnitStruct 大小: {} 字节", mem::size_of::<UnitStruct>());
// 所有输出都是 0 字节!
}
结构体数据的所有权
在前面的 User
结构体的定义中,我们使用了自身拥有所有权的 String
类型而不是 &str
字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
可以使结构体存储被其他对象拥有的数据的引用,不过这么做的话需要用上生命周期(lifetimes):
struct User<'a> {
active: bool,
username: &'a str,
email: &'a str,
sign_in_count: u64,
}
fn main() {
let username = "someusername123";
let email = "someone@example.com";
let user1 = User {
active: true,
username,
email,
sign_in_count: 1,
};
println!("用户名: {}", user1.username);
}
打印结构体
为了能够打印结构体,需要为结构体实现 Debug
trait:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
println!("rect1 is {:#?}", rect1); // 更好的格式化输出
}
关于 #[derive] 语法
你可能注意到了 #[derive(Debug)]
这个以 #
开头的语法。这是 Rust 中的属性(Attribute)语法:
#[derive(Debug)]
是一个派生属性,告诉编译器自动为Rectangle
结构体实现Debug
trait#
开头的语法称为属性,用于为代码添加元数据或指令derive
是最常用的属性之一,可以自动实现常见的 trait
// 可以同时派生多个 trait
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone(); // 使用 Clone trait
println!("{:?}", p1); // 使用 Debug trait
println!("相等吗?{}", p1 == p2); // 使用 PartialEq trait
}
💡 详细了解属性语法:关于
#[derive]
和其他属性的详细说明,请参考 属性(Attributes) 章节。
方法语法
方法(method)与函数类似:它们使用 fn
关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义,并且它们第一个参数总是 self
,它代表调用该方法的结构体实例。
定义方法
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn width(&self) -> bool {
self.width > 0
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
println!("矩形的面积是 {} 平方像素。", rect1.area());
if rect1.width() {
println!("矩形的宽度为 {}", rect1.width);
}
println!("rect1 能容纳 rect2 吗?{}", rect1.can_hold(&rect2));
}
自动引用和解引用
当使用 object.something()
调用方法时,Rust 会自动为 object
添加 &
、&mut
或 *
以便使 object
与方法签名匹配:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
// 这些调用是等价的
println!("{}", rect1.area());
println!("{}", (&rect1).area());
println!("{}", Rectangle::area(&rect1));
}
关联函数
所有在 impl
块中定义的函数被称为关联函数(associated functions),因为它们与 impl
后面命名的类型相关。我们可以定义不以 self
为第一参数的关联函数(因此不是方法),因为它们并不作用于一个结构体的实例:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Rectangle {
Rectangle {
width,
height,
}
}
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle::new(30, 50);
let sq = Rectangle::square(3);
println!("矩形面积: {}", rect.area());
println!("正方形面积: {}", sq.area());
}
多个 impl 块
每个结构体都允许拥有多个 impl
块:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
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
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
println!("面积: {}", rect1.area());
println!("能容纳: {}", rect1.can_hold(&rect2));
}
实际应用示例
用户管理系统
#[derive(Debug)]
struct User {
id: u32,
username: String,
email: String,
active: bool,
}
impl User {
fn new(id: u32, username: String, email: String) -> User {
User {
id,
username,
email,
active: true,
}
}
fn deactivate(&mut self) {
self.active = false;
}
fn change_email(&mut self, new_email: String) {
self.email = new_email;
}
fn display_info(&self) {
println!("用户 {}: {} ({})", self.id, self.username, self.email);
println!("状态: {}", if self.active { "活跃" } else { "非活跃" });
}
}
fn main() {
let mut user = User::new(1, "张三".to_string(), "zhangsan@example.com".to_string());
user.display_info();
user.change_email("zhangsan_new@example.com".to_string());
user.deactivate();
user.display_info();
}
几何图形计算
#[derive(Debug)]
struct Circle {
radius: f64,
}
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
impl Circle {
fn new(radius: f64) -> Circle {
Circle { radius }
}
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn circumference(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
}
impl Rectangle {
fn new(width: f64, height: f64) -> Rectangle {
Rectangle { width, height }
}
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
}
fn main() {
let circle = Circle::new(5.0);
let rectangle = Rectangle::new(4.0, 6.0);
println!("圆形面积: {:.2}", circle.area());
println!("圆形周长: {:.2}", circle.circumference());
println!("矩形面积: {:.2}", rectangle.area());
println!("矩形周长: {:.2}", rectangle.perimeter());
}
最佳实践
- 使用有意义的字段名:字段名应该清楚地表达其用途
- 合理使用所有权:考虑是否需要拥有数据还是借用数据
- 实现常用 trait:如
Debug
、Clone
、PartialEq
等 - 使用关联函数作为构造器:提供便利的创建方法
- 将相关功能组织在 impl 块中:保持代码组织清晰
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Point {
Point { x, y }
}
fn origin() -> Point {
Point { x: 0.0, y: 0.0 }
}
fn distance_from_origin(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
fn distance_to(&self, other: &Point) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}
fn main() {
let p1 = Point::new(3.0, 4.0);
let p2 = Point::origin();
println!("点1到原点的距离: {:.2}", p1.distance_from_origin());
println!("两点间距离: {:.2}", p1.distance_to(&p2));
}