结构体方法
本教程共 78 篇 · 第 27 篇 · 更新于 2026-08-08 · 约 10 分钟阅读
本节目标:用
impl块给结构体定义方法,理解&self如何代表调用者自身,分清方法和普通函数的差别,让结构体既能装数据也能干活。
上一章的结构体只是”数据容器”。但现实中,数据往往要配行为——比如”矩形”该能”算面积”。Rust 不给结构体开 class 那种 class,而是用 impl(implementation,实现)块把函数绑到类型上,这种绑定的函数叫方法(method)。数据和行为的分离,是 Rust 设计上很关键的一点:类型定义和数据”长什么样”,impl 块负责它能”干什么”。
1-1 在 impl 里定义方法
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("面积是 {}", rect.area()); // 1500
}
impl Rectangle { ... } 里写的 area 就是方法。调用时 rect.area(),编译器自动把 rect 作为 &self 传进去。self 代表”调用这个方法的那个实例自己”。你可以把 self 理解成”这句话是谁说的”——rect.area() 里 self 就是 rect。
Note为什么参数写
&self而不是self?&self是”借用自己的引用”,不拿走所有权、调用后rect还能用。绝大多数读数据的方法都用&self。这正呼应前面学的借用:你只是想看一眼数据,没必要把整个实例的所有权搬进方法里。
1-2 self 的几种形态
self 前能加和变量一样的修饰,决定方法怎么对待调用者:
&self:借不可变引用,只读、不拿走。最常用。&mut self:借可变引用,方法内能改实例的字段。self:拿走所有权,方法结束后实例归方法所有(调用方不能再用它)。少见,常用于”消耗掉自己”的场景。
impl Rectangle {
fn set_width(&mut self, w: u32) {
self.width = w; // 能改,因为是 &mut self
}
}
fn main() {
let mut rect = Rectangle { width: 30, height: 50 };
rect.set_width(40);
println!("{}", rect.width); // 40
}
set_width 用 &mut self,所以能改 self.width。调用方 rect 必须是 let mut。这又一次体现了借用规则:要改,就得是可变借用。
1-3 方法 vs 普通函数
方法和函数几乎一样,唯一区别:方法第一个参数是 self(或 &self / &mut self),写在与类型关联的 impl 里,调用时用 实例.方法() 语法。函数则独立存在、调用时不绑实例。
方法的好处是能直接访问实例字段(self.width),不用把字段一个个传进去。代码读起来也直观:“矩形.算面积()“比”算面积(矩形)“更贴近人类说话。而且方法天然把”属于 Rectangle 的逻辑”聚在一起,维护和阅读都更省心。
Warning新手常把
rect.area写成rect.area(漏括号),在 Rust 里那只是”拿到函数本身”而不是调用。方法调用一定要有(),哪怕没参数:rect.area()。漏了括号编译器会警告”这个函数没被调用”,变量也没被用到。
1-4 方法可以调方法
一个方法里能调用同一个实例的其他方法,用 self. 前缀:
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.area() > other.area() // 调用自身另一个方法
}
}
can_hold 里用 self.area() 复用面积计算,逻辑不重复。这是方法组织代码的便利——把小功能拆成方法,再在大方法里组合,避免复制粘贴。
1-5 可以有多个 impl 块
Rust 允许同一个类型有多个 impl 块,分散写也行:
impl Rectangle {
fn area(&self) -> u32 { self.width * self.height }
}
impl Rectangle {
fn new_square(size: u32) -> Rectangle { /* 见下一章 */ }
}
多个 impl 合起来都算这个类型的方法。通常你把所有方法放一个 impl 里就好,拆多个是语法允许的灵活性——比如方法很多时按功能分组,各放一个 impl 块,可读性更好。
1-6 自动引用与解引用
调用 rect.area() 时,即便 area 要的是 &self,你写 rect(不是 &rect)也行——Rust 会自动补上 &、&mut 或 *,把你给的值调成方法需要的形态。这叫”自动引用”。所以你几乎不用手动写 (&rect).area(),直接 rect.area() 即可。
Tip这套自动引用/解引用机制和前面讲的
*解引用是一套体系。新手阶段不用深究触发规则,记住”方法调用时直接写实例名,编译器会帮你取引用”就够了。这也是为什么你从没见过(&rect).area()这种写法——编译器替你省了。
1-7 方法里 self 与参数的配合
方法里 &self 和参数怎么配合,值得再看一个例子。比如给矩形加”判断是否包含另一个矩形”的方法,它既读自己的字段,又接收一个别的实例:
impl Rectangle {
fn contains(&self, other: &Rectangle) -> bool {
self.x <= other.x
&& self.y <= other.y
&& self.width >= other.width
&& self.height >= other.height
}
}
这里 &self 是调用者(不可变借用自己),other: &Rectangle 是借来的另一个矩形。两个都只读,都不拿走所有权,调用方之后还能继续用两者。方法里既能访问 self.x,也能访问参数 other.x,层次分明。
Tip写方法时默认用
&self。只有当你确实需要”消耗掉这个实例”(比如把一个对象转成另一种类型、且不再需要原对象)时,才用self(不带&)。新手绝大多数方法用&self就够了,别一上来就写self,否则调用一次实例就没了,会困惑”为什么调完方法变量失效了”。
Note方法和关联函数都写在
impl块里,区别只看有没有self参数。一个impl块里可以混着写:既有&self的方法,也有不带self的关联函数(如new)。它们都属于这个类型,调用时分别用实例.方法()和类型::函数()。
1-8 读取字段的方法命名约定
Rust 里给”读取字段”的方法有个命名约定:直接叫字段名,不加 get_ 前缀。比如读矩形宽,方法就叫 width(&self) -> u32 而非 get_width:
impl Rectangle {
fn width(&self) -> u32 {
self.width
}
}
这和其他语言(如 Java 的 getWidth())不同。Rust 社区偏好”字段同名的方法即 getter”,调用时 rect.width() 读起来像在取属性。需要计算才得到的值,也同样用这个风格(如 area())。
Note之所以不加
get_前缀,是因为 Rust 的方法调用语法rect.width()本身已经表达了”这是一次访问”,再加get是冗余。这是约定俗成的风格,写出来符合社区习惯、也更易读。当字段是私有时,这种同名方法还能当”对外只读窗口”,封装性更好。
1-9 你可能会踩的坑
- 坑一:方法调用漏括号,
rect.area拿到的是函数不是结果。记住方法一定带()。 - 坑二:想改字段却用了
&self,编译器报错”不能修改不可变借用”。要改就改成&mut self,且调用方得是let mut。 - 坑三:写了
self(不带&)想”用完还回来”,结果实例被方法吞掉,调用方之后用不了。除非真要消耗实例,否则用&self。
Warning别把”数据和行为绑一起”理解成 Rust 在模仿面向对象 class。Rust 的
impl只是”把函数关联到类型”,没有继承、没有 class 那种层级。方法和数据的耦合是松散的——同一个类型可以有多个 impl 块,方法也不会自动访问”父类”。放下 class 的思维,按”类型 + 关联函数”去理解,会顺畅很多。
1-10 方法里的 self 借用细节
&self、&mut self、self 三者直接关系到”调用后原实例还在不在”。用 &self 的方法只是”看一眼”实例,调用完实例安然无恙、还能继续用;用 &mut self 的方法会”改动”实例,同样调用完还在;只有写 self(不带 &)的方法会”拿走”实例的所有权,调用之后原变量就失效了——这通常发生在”消费型”操作上,比如把结构体转成另一种类型。大多数读写操作都用 &self / &mut self,放心,实例不会丢。
Note一个类型里可以既有
&self方法也有&mut self方法,编译器会按你调用时的实例是否可变来选合适的那一个。如果实例声明为let mut,两种都能调;若只是let(不可变),就只能调&self的方法,强行调&mut self会报错。这把”可变性”在方法层面也管住了。
还有个实用点:方法可以返回 &self 或 &mut self,从而支持”链式调用”——像 obj.do_a().do_b() 那样把多次操作串成一行。标准库里 String 的很多方法就返回 &mut self,所以能写 s.push_str("a").push_str("b")。想让自己的结构体也支持链式调用,让方法返回 self(值)或 &mut self(借用)即可,按是否要转移所有权选择。
1-11 小结
用 impl 类型 { fn 方法(&self) {} } 给结构体绑方法;&self 借实例自身、最常用,&mut self 可改;方法能互调;调用必须带 ()。方法和”关联函数”常被混为一谈——下一章澄清:不带 self、用 类型::函数() 调用的叫关联函数(如 String::from),它常用于构造函数。
到这你已经掌握了自定义类型的数据与行为。下一章讲关联函数后,我们就转向枚举(enum)——Rust 另一大自定义类型,配合模式匹配威力巨大。