Skip to content

Day 7: 结构体

今天是《30天精通Rust语言》系列的第七天。我们将学习结构体(Struct),这是Rust中定义自定义数据类型的主要方式。结构体允许你将相关数据组织在一起,形成有意义的数据单元。

struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("user@example.com"),
username: String::from("rustacean"),
active: true,
sign_in_count: 1,
};
println!("User: {}, {}", user1.username, user1.email);
}

结构体实例需要整体标记为 mut 才能修改其字段:

fn main() {
let mut user1 = User {
email: String::from("user@example.com"),
username: String::from("rustacean"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("new@example.com");
println!("Updated email: {}", user1.email);
}

注意:不能部分可变,字段级别的可变暂不支持。

元组结构体(Tuple Struct)没有字段名,只有类型:

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: ({}, {}, {})", black.0, black.1, black.2);
println!("Origin: ({}, {}, {})", origin.0, origin.1, origin.2);
}

元组结构体适合当你需要给元组起一个名字,但又不需要命名字段的场景。

没有任何字段的结构体称为类单元结构体(Unit-like Struct),适合需要实现trait但不需要存储数据的场景:

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

方法(Method)是关联到结构体上的函数,第一个参数是 self,表示调用方法的结构体实例。

struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// &self 表示不可变借用
fn area(&self) -> u32 {
self.width * self.height
}
// &mut self 表示可变借用
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
// 静态方法(关联函数),不借用了self
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}
fn main() {
let mut rect = Rectangle::new(30, 50);
println!("Area: {}", rect.area());
rect.scale(2);
println!("Scaled: {}x{}", rect.width, rect.height);
}

方法可以获取 self 的所有权,或借用 self:

impl Rectangle {
// 消耗self,调用后rect不再可用
fn destroy(self) {
println!("Destroying Rectangle {}x{}", self.width, self.height);
}
// 借用self(不可变)
fn describe(&self) {
println!("Rectangle: {}x{}", self.width, self.height);
}
}

关联函数是不以 self 为参数的函数,常用于构造器模式:

impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let sq = Rectangle::square(10);
println!("Square: {}x{}", sq.width, sq.height);
}

结构体字段的所有权遵循所有权规则:

struct User {
username: String,
email: String,
}
fn main() {
let user = User {
email: String::from("user@example.com"),
username: String::from("rustacean"),
};
// user离开作用域时,username和email都会被drop
}

如果字段需要引用而非拥有,需要使用生命周期注解(下一章内容)。

让我们创建一个完整的示例,综合运用结构体的各种特性:

struct BankAccount {
account_id: u32,
holder_name: String,
balance: f64,
}
impl BankAccount {
fn new(id: u32, name: &str) -> BankAccount {
BankAccount {
account_id: id,
holder_name: String::from(name),
balance: 0.0,
}
}
fn deposit(&mut self, amount: f64) {
if amount > 0.0 {
self.balance += amount;
println!("Deposited {}. New balance: {}", amount, self.balance);
} else {
println!("Invalid deposit amount");
}
}
fn withdraw(&mut self, amount: f64) {
if amount > self.balance {
println!("Insufficient balance!");
} else if amount <= 0.0 {
println!("Invalid withdrawal amount");
} else {
self.balance -= amount;
println!("Withdrew {}. New balance: {}", amount, self.balance);
}
}
fn balance(&self) -> f64 {
self.balance
}
}
fn main() {
let mut account = BankAccount::new(1001, "Alice");
println!("Account created for {}", account.holder_name);
account.deposit(1000.0);
account.withdraw(300.0);
println!("Final balance: {}", account.balance());
}

今天的实践任务:

  1. 定义一个 Book 结构体,包含 title(String)、author(String)、year(u32)字段。
  2. 为 Book 实现一个 new 关联函数和 info 方法。
  3. 创建两个 Book 实例并调用它们的方法。
  4. 实现一个 Stack<T> 结构体(泛型),包含 push、pop、is_empty 方法。
  5. 修改 BankAccount 示例,添加利息计算功能。

问:结构体和方法为什么不放在同一个文件中?

答:Rust推荐将 impl 块与结构体定义放在同一个文件,这是标准做法。在大型项目中,你可能会用多个 impl 块实现不同的 trait。

问:什么时候用元组结构体而不是普通结构体?

答:当字段有独立含义但不需要命名时,或作为类型别名时使用元组结构体。例如 struct Point(f32, f32) 比 struct Point { x: f32, y: f32 } 更简洁。

今天我们学习了Rust的结构体:

  • 结构体是自定义数据类型,将相关字段组合在一起。
  • 元组结构体和类单元结构体提供了不同的组织方式。
  • 方法通过 impl 块定义,第一个参数是 self。
  • 关联函数(静态方法)常用于构造器模式。
  • 结构体字段遵循所有权规则。

明天我们将学习枚举与模式匹配,这是Rust中另一个强大的特性。加油!