4.2 方法
方法(Method)是与结构体(或其他自定义类型)关联的函数。它们定义在impl块中,允许你为自定义类型赋予特定的行为,从而实现数据与行为的封装。Rust 中的方法分为两类:实例方法(与具体实例绑定)和关联函数(与类型本身绑定,最常见的就是new构造函数)。
4.2.1 在结构体上定义方法
要为一个结构体定义方法,需要使用impl(implementation)关键字。在impl块内定义的函数,其第一个参数必须是某种形式的self,这样它才能成为实例方法,并通过点号(.)语法在实例上调用。
示例:为 Rectangle 结构体定义方法
struct Rectangle { width: u32, height: u32, } impl Rectangle { // 定义一个实例方法 area fn area(&self) -> u32 { self.width * self.height } // 定义另一个实例方法 can_hold 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 的面积是: {} 平方像素", rect1.area()); println!("rect1 能容纳 rect2 吗? {}", rect1.can_hold(&rect2)); }方法 vs 普通函数:
- 调用方式不同:方法使用
instance.method()的点号语法调用,而普通函数使用function()直接调用。 - 自动引用:Rust 会根据方法的
self参数类型,自动为实例添加引用(&)、可变引用(&mut)或直接取得所有权。你不需要在调用时手动写&rect1。 - 语义更清晰:方法明确表达了"这个行为属于这个类型",使代码更具可读性和组织性。
多个
impl块:Rust 允许为同一个类型定义多个impl块。这在大型项目中很有用,可以将不同功能的方法分组,或者将方法的定义和 trait 实现分开。
impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } impl Rectangle { fn perimeter(&self) -> u32 { 2 * (self.width + self.height) } }4.2.2 self 参数
方法的第一个参数决定了该方法如何"拥有"或"借用"调用它的实例。Rust 提供了三种self参数的形式,每种对应不同的所有权语义:
| 形式 | 含义 | 对实例的影响 | 使用场景 |
|---|---|---|---|
&self | 不可变借用 | 实例保持不变,可继续使用 | 只读取数据,不修改 |
&mut self | 可变借用 | 可以修改实例的字段 | 需要修改实例的数据 |
self | 取得所有权 | 实例被消耗,调用后原变量失效 | 需要消耗或转换实例 |
示例:三种 self 参数的对比
#[derive(Debug)] struct Counter { value: i32, } impl Counter { // &self:不可变借用,只读取 fn display(&self) { println!("当前值: {}", self.value); } // &mut self:可变借用,可以修改 fn increment(&mut self) { self.value += 1; } // self:取得所有权,消耗实例 fn into_value(self) -> i32 { self.value } } fn main() { let mut counter = Counter { value: 0 }; counter.display(); // 使用 &self,counter 之后仍可使用 counter.increment(); // 使用 &mut self,counter 之后仍可使用 println!("{:?}", counter); // counter 仍然有效 let val = counter.into_value(); // 使用 self,counter 的所有权被转移 // counter.display(); // 编译错误!counter 已被消耗,不再有效 println!("最终值: {}", val); }选择哪种 self 的指导原则:
- 优先使用
&self:如果方法不需要修改实例,这是最安全、最灵活的选择。 - 需要修改时使用
&mut self:当方法需要改变实例的状态时。 - 谨慎使用
self:只在确实需要消耗实例时使用,例如将实例转换为另一种类型,或实现类似into_value这样的"消费并返回"模式。
4.2.3 关联函数
关联函数是定义在impl块中,但不以self作为参数的函数。它们不属于任何具体的实例,而是属于类型本身。最常见的关联函数是构造函数new,用于创建并返回类型的新实例。
关联函数通过双冒号::语法调用,而不是点号语法。
示例:定义和调用关联函数
struct Rectangle { width: u32, height: u32, } impl Rectangle { // 关联函数:没有 self 参数,通常用作构造函数 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(10); // 通过 . 语法调用实例方法 println!("矩形面积: {}", rect.area()); println!("正方形面积: {}", sq.area()); }关联函数的特点:
- 无 self 参数:这是区分关联函数和实例方法的关键标志。
- 命名空间:关联函数通常用于提供与类型相关的工具函数或工厂方法。
- 常见命名约定:
new()— 最常用的构造函数名。from_xxx()— 从某种特定数据创建实例,如from_str()。default()— 创建具有默认值的实例。with_xxx()— 创建实例并设置某些特定属性,如with_capacity()。
一个类型可以有多个关联函数,它们为创建实例提供了不同的途径,使 API 更加灵活和直观。
impl Rectangle { fn new(width: u32, height: u32) -> Self { Self { width, height } } fn from_diameter(diameter: u32) -> Self { Self { width: diameter, height: diameter, } } fn unit_square() -> Self { Self::new(1, 1) } }
Self类型别名:在impl块中,Self是当前类型的别名。在上面的例子中,Self等价于Rectangle。使用Self可以让代码更简洁,也便于将来重构时减少修改量。