首页 / Rust 入门教程 / 结构体方法

Rust 入门教程

结构体方法

本教程共 78 篇 · 第 27 篇 · 更新于 2026-08-08 · 约 10 分钟阅读

RustRust 入门教程struct方法implself面向对象

本节目标:用 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 selfself 三者直接关系到”调用后原实例还在不在”。用 &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 另一大自定义类型,配合模式匹配威力巨大。