本案例将深入讲解 Rust 中结构体的方法(methods)与关联函数(associated functions)的定义与使用。通过一个“二维平面上点”的具体模型,我们将学习如何为结构体添加行为、如何区分
&self、&mut self和Self的使用场景,并掌握impl块的基本语法和最佳实践。这是理解 Rust 面向数据封装编程范式的关键一步。
一、什么是结构体方法与关联函数?
在 Rust 中,结构体(struct) 是组织数据的核心方式之一。然而,仅有数据是不够的——我们还需要为这些数据赋予行为。这就引出了两个重要概念:
- 方法(Methods):定义在
impl块中、第一个参数为self、&self或&mut self的函数,用于操作结构体实例。 - 关联函数(Associated Functions):同样定义在
impl块中,但不以self为参数,通常用作构造器(如new)或其他工具性功能。
Rust 不支持传统意义上的“类”,但它通过结构体 + impl 块实现了类似面向对象的封装特性。
二、代码演示:二维点结构体及其方法实现
下面我们以一个表示二维坐标点的结构体 Point 为例,完整展示方法与关联函数的编写过程。
1. 定义结构体
#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
x: f64,
y: f64,
}
这里我们使用了几个有用的派生 trait:
Debug:允许使用{:?}打印调试信息;Clone和Copy:使Point可复制(因为所有字段都是Copy类型);PartialEq:支持相等比较。
2. 实现方法与关联函数
impl Point {
// 关联函数:构造一个新的 Point 实例(常用作构造器)
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
// 方法:获取该点的 x 坐标(只读访问)
pub fn get_x(&self) -> f64 {
self.x
}
// 方法:获取该点的 y 坐标
pub fn get_y(&self) -> f64 {
self.y
}
// 方法:设置新的坐标值(可变借用)
pub fn set(&mut self, x: f64, y: f64) {
self.x = x;
self.y = y;
}
// 方法:计算到原点 (0,0) 的距离
pub fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
// 方法:计算到另一个点的距离
pub fn distance_to(&self, other: &Self) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
// 方法:移动当前点(可变引用)
pub fn translate(&mut self, dx: f64, dy: f64) {
self.x += dx;
self.y += dy;
}
// 方法:判断是否位于第一象限
pub fn is_in_first_quadrant(&self) -> bool {
self.x > 0.0 && self.y > 0.0
}
// 关联函数:创建单位圆上的点(给定角度,弧度制)
pub fn from_angle(angle: f64) -> Self {
Self {
x: angle.cos(),
y: angle.sin(),
}
}
// 关联函数:获取零点(原点)
pub fn origin() -> Self {
Self::new(0.0, 0.0)
}
}
3. 主函数调用示例
fn main() {
// 使用关联函数创建实例
let mut p1 = Point::new(3.0, 4.0);
let p2 = Point::new(0.0, 0.0);
let unit_point = Point::from_angle(std::f64::consts::PI / 4.0); // 45度方向的单位向量
println!("p1 = {:?}", p1); // p1 = Point { x: 3.0, y: 4.0 }
println!("p2 = {:?}", p2); // p2 = Point { x: 0.0, y: 0.0 }
println!("unit_point = {:?}", unit_point); // 大约 (0.707, 0.707)
// 调用方法
println!("p1 到原点距离: {:.2}", p1.distance_from_origin()); // 5.00
println!("p1 到 p2 距离: {:.2}", p1.distance_to(&p2)); // 5.00
// 修改点的位置
p1.translate(1.0, -1.0);
println!("平移后 p1 = {:?}", p1); // (4.0, 3.0)
// 检查象限
if p1.is_in_first_quadrant() {
println!("p1 位于第一象限");
}
// 设置新坐标
p1.set(0.0, 0.0);
println!("重置后 p1 = {:?}", p1);
// 使用静态方法获取原点
let origin = Point::origin();
println!("原点 = {:?}", origin);
}
输出结果如下:
p1 = Point { x: 3.0, y: 4.0 }
p2 = Point { x: 0.0, y: 0.0 }
unit_point = Point { x: 0.7071067811865476, y: 0.7071067811865475 }
p1 到原点距离: 5.00
p1 到 p2 距离: 5.00
平移后 p1 = Point { x: 4.0, y: 3.0 }
p1 位于第一象限
重置后 p1 = Point { x: 0.0, y: 0.0 }
原点 = Point { x: 0.0, y: 0.0 }
三、关键字高亮说明
以下是本案例中涉及的关键字及其作用解析:
| 关键字/符号 | 高亮颜色建议 | 含义说明 |
|---|---|---|
struct | 黄色 | 定义自定义数据结构 |
impl | 黄色 | 为类型实现方法或关联函数 |
Self | 紫色 | 当前类型别名,在 impl 块中指代 Point |
self | 橙色 | 表示当前实例的所有权转移(少用) |
&self | 橙色 | 不可变借用实例,最常见于只读方法 |
&mut self | 橙色 | 可变借用实例,用于修改内部状态 |
pub | 青色 | 控制可见性,公开接口供外部调用 |
fn | 绿色 | 函数声明关键字 |
-> | 红色 | 表示函数返回类型 |
:: | 白色 | 路径分隔符,如 Point::new() |
✅ 提示:在 IDE(如 VS Code + Rust Analyzer)中,上述关键字会自动着色,帮助你快速识别语义结构。
四、数据表格:方法分类对比
下表总结了不同类型的方法与关联函数的特点:
| 类型 | 第一个参数 | 是否消耗实例 | 是否可修改数据 | 典型用途 | 示例 |
|---|---|---|---|---|---|
| 不可变方法 | &self | 否 | 否 | 查询属性、计算值 | distance_to, get_x |
| 可变方法 | &mut self | 否 | 是 | 修改内部状态 | set, translate |
| 所有权方法 | self | 是 | 是(之后无法使用) | 转换或销毁对象 | into_string(标准库例子) |
| 关联函数 | 无 self | 否 | 视情况而定 | 构造器、工厂函数、工具函数 | new, origin, from_angle |
⚠️ 注意:虽然可以写
fn do_something(self),但在实践中较少见,除非你想在方法调用后“消费”该对象。
五、分阶段学习路径
为了系统掌握结构体方法与关联函数,推荐按照以下五个阶段循序渐进地学习:
🔹 阶段一:理解结构体基础(已掌握)
- 学会使用
struct定义具名字段结构体; - 理解结构体实例化与字段访问语法;
- 掌握
#[derive(...)]宏的常见用法。
✅ 目标达成标志:能独立写出包含多个字段的结构体并初始化。
🔹 阶段二:掌握 impl 块基本语法
- 学会在
impl块中定义函数; - 区分普通函数与方法的区别;
- 理解
Self在impl块中的意义。
📌 练习任务:
impl Point {
fn info(&self) -> String {
format!("Point({:.2}, {:.2})", self.x, self.y)
}
}
✅ 目标达成标志:能在 impl 块中正确使用 Self 并返回格式化字符串。
🔹 阶段三:熟练使用三种 self 形式
| 参数形式 | 使用场景 | 内存影响 |
|---|---|---|
&self | 读取数据 | 最高效,允许多次借用 |
&mut self | 修改数据 | 独占可变借用,防止数据竞争 |
self | 消费实例 | 移动语义,常用于转换类型 |
📌 练习任务:实现一个 into_tuple(self) 方法,将 Point 转为 (f64, f64) 并释放原对象。
impl Point {
fn into_tuple(self) -> (f64, f64) {
(self.x, self.y)
}
}
✅ 目标达成标志:理解 move 语义并在适当场合使用 self 参数。
🔹 阶段四:设计合理的关联函数
重点掌握:
- 构造函数命名惯例:
new,default,with_...; - 工厂模式:根据条件创建不同实例;
- 工具函数:不依赖实例的辅助功能。
📌 练习任务:添加 with_polar(r, theta) 关联函数,从极坐标创建点。
impl Point {
pub fn with_polar(r: f64, theta: f64) -> Self {
Self {
x: r * theta.cos(),
y: r * theta.sin(),
}
}
}
✅ 目标达成标志:能够脱离实例上下文设计实用的构造逻辑。
🔹 阶段五:综合应用与最佳实践
- 将方法组织成清晰的模块;
- 使用文档注释(
///)生成 API 文档; - 遵循 Rust API 设计指南(如
new返回Self,避免冗余前缀); - 结合泛型与 trait 进一步扩展能力。
📌 最佳实践示例:
/// 代表二维空间中的一个点
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point {
/// X 坐标
pub x: f64,
/// Y 坐标
pub y: f64,
}
impl Point {
/// 创建一个新的点
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
/// 计算到另一点的距离
pub fn distance(&self, other: &Self) -> f64 {
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
}
}
运行 cargo doc --open 可查看生成的 HTML 文档。
✅ 目标达成标志:写出具备良好文档、清晰接口、符合社区规范的结构体实现。
六、核心要点总结
✅ 本案例核心收获:
-
方法必须定义在
impl块中
所有与结构体相关的行为都应集中在此块内,保持代码组织清晰。 -
&self是最常见的方法接收者
绝大多数查询类方法应使用&self,保证性能与安全性。 -
&mut self用于修改状态
当需要改变字段值时,使用可变借用,注意其独占性限制。 -
关联函数不接收
self
常用于构造器(如new)、工厂函数或数学变换。 -
Self是类型别名,提升可维护性
使用Self替代硬编码类型名,便于未来重构。 -
合理使用
#[derive]提升开发效率
如Debug、Clone、Copy、PartialEq等 trait 极大简化常见操作。 -
方法调用语法糖
.()自动解引用
Rust 会自动处理&point.distance_to(&other)中的引用匹配,无需手动取地址。
七、常见误区与避坑指南
| 错误写法 | 正确做法 | 原因说明 |
|---|---|---|
fn new() -> point | fn new() -> Self | 类型名应大写,且推荐使用 Self |
fn set(self, x: f64, y: f64) | fn set(&mut self, ...) | self 会导致实例被移动,无法继续使用 |
忘记加 pub 导致外部无法调用 | 显式标注 pub | 默认私有,需主动暴露接口 |
在方法中错误地返回 &self.x | 返回 self.x(值拷贝) | f64 实现了 Copy,无需引用 |
多个 impl 块之间冲突 | 每个类型可有多个 impl 块 | 合法,可用于分离关注点 |
八、延伸思考:方法 vs 函数
有人可能会问:“为什么不直接写个普通函数,比如 fn distance(p1: &Point, p2: &Point)?”
答案是:方法提供了更好的封装性和表达力。
- ✅ 更自然的调用方式:
p1.distance_to(&p2)比distance(&p1, &p2)更贴近直觉; - ✅ 支持链式调用:
point.translate(1.0, 1.0).distance_from_origin(); - ✅ 更强的模块化:行为与数据绑定在一起,易于维护。
这也是为什么 Rust 标准库大量使用方法而非自由函数的原因。
九、小结
在本案例中,我们通过构建一个简单的 Point 结构体,全面掌握了 Rust 中结构体方法与关联函数的定义与使用技巧。我们学会了:
- 如何使用
impl块为结构体添加行为; - 区分
&self、&mut self和Self的语义差异; - 设计合理的构造器与工具函数;
- 利用
Self提高代码可维护性; - 遵循社区最佳实践编写高质量的 Rust 代码。
这些技能是后续学习更复杂类型(如枚举、泛型结构体)和 trait 实现的基础。随着你对 Rust 的深入,你会发现这种“数据 + 行为”的组合方式既安全又高效,完美体现了 Rust “零成本抽象”的设计理念。
🎯 下一步建议:尝试为 Rectangle 或 Circle 结构体实现面积、周长等方法,并结合 enum Shape 和 trait Draw 进行多态设计,为进入第27~29案例打下坚实基础。
✅ 本案例完成目标检查清单:
- 理解
impl块的作用 - 掌握
&self/&mut self/Self的使用 - 能够定义关联函数作为构造器
- 熟悉常见 trait 派生的用途
- 编写了完整的结构体方法集并测试运行
2717

被折叠的 条评论
为什么被折叠?



