Rust 中的方法与关联函数详解

1. 方法(Methods)是什么?

在 Rust 里,方法和函数的定义方式很像:

  • 都使用 fn 来声明。
  • 都能拥有参数和返回值。
  • 都包含一段在被调用时执行的代码逻辑。

不同点在于: 方法必须定义在某个具体类型(比如 structenum 或者在某个 trait 对象里)的上下文中。而且方法的第一个参数固定要写成 self(可以是 self&self 或者 &mut self),用来代表调用该方法的具体实例。

让我们来看看一个简单示例。假设我们有一个 Rectangle 结构体:

rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

如果你想为 Rectangle 实例添加一个计算面积的功能,我们可以在 impl(implementation)块中为它定义一个方法 area

rust 复制代码
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
  • 这里 impl Rectangle { ... } 表示这个块里的所有函数都与 Rectangle 类型相关联。
  • fn area(&self) -> u32 说明:这是一个方法,第一个参数是 &self,表示以不可变引用的形式 访问当前调用该方法的 Rectangle 实例。
  • self.widthself.height 即代表该实例的字段。用 self 访问字段非常直观。

main 函数中,当我们创建一个矩形实例后,就可以使用方法语法来获取面积:

rust 复制代码
fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    
    println!("rect1 的面积是:{}", rect1.area());
}

运行后,会输出:

复制代码
rect1 的面积是:1500

2. 为什么要使用 &self 而不是 &Rectangle?

在我们将 area 从一个普通函数重构为一个方法时,你会注意到,函数签名由原本的

rust 复制代码
fn area(rectangle: &Rectangle) -> u32 { ... }

变为

rust 复制代码
fn area(&self) -> u32 { ... }

这是因为在 impl Rectangle 这个上下文中,Rust 给出了一个更具可读性的方式:让第一个参数自动变为 self,而 Self 则是当前实现块对应的类型别名。如果你需要修改 实例的字段,你可以将第一个参数写为 &mut self;如果需要获取所有权 并可能在方法内部把它"转化"成别的东西,则用 self。但这种把所有权转移给方法本身的做法很少见。

在大多数情况下,我们只是想读一下结构体数据而不改变它,这时使用 &self 最为常见,也能让调用者继续使用这个实例。


3. 同名字段与同名方法

如果你在 Rectangle 内也有一个字段叫做 width,同时还想定义一个方法也叫 width,这是合法的。比如:

rust 复制代码
impl Rectangle {
    fn width(&self) -> bool {
        self.width > 0
    }
}

在调用时:

  • rect.width (不带括号)访问的是字段 width 的数值。
  • rect.width() (带括号)调用的是同名方法,返回一个布尔值。

在很多语言中,如果你只想单纯地返回字段值,会把这种方法称为"getter"。Rust 并不会为你自动生成 getter,但你可以自行定义。这样一来,你可以只把字段设为私有,但对外公开这个只读方法,让外部安全地访问它。


4. 借用与解引用:为什么在调用方法时不需要写 & 或 *?

在 C/C++ 中,如果你要通过指针来调用成员函数,需要写 ->。或者,如果你手头是指针,还要显式地 (*object).method() 等。在 Rust 中则不需要这么麻烦,因为自动引用和解引用 让你可以直接写 object.method()

实际上,这些调用是一样的:

rust 复制代码
p1.distance(&p2);
(&p1).distance(&p2);

Rust 会根据方法签名(第一个参数是 &self&mut self 还是 self)来自动推断是否需要帮你加 &&mut 或者 *。这大大简化了调用方法时的语法。


5. 方法可以拥有多个参数

方法和函数在参数上并没什么区别,除了第一个参数是 self 以外,其他参数你可以自由添加。举例来说,为 Rectangle 再定义一个方法 can_hold,用来检查"当前矩形"是否可以完全容纳另一个矩形:

rust 复制代码
impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

然后这样使用它:

rust 复制代码
fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); // true
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); // false
}

6. 关联函数(Associated Functions)

如果在 impl 块中定义的函数没有 self 参数,那它就不是方法(method),而是关联函数(associated function)。关联函数常被用来提供类似"构造函数"的功能。举个例子,如果你想快速构造一个"正方形":

rust 复制代码
impl Rectangle {
    // 关联函数
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}

调用的时候,使用 :: 语法来调用关联函数:

rust 复制代码
fn main() {
    let sq = Rectangle::square(3);
    println!("正方形 sq: {:#?}", sq);
}

打印结果为:

复制代码
正方形 sq: Rectangle {
    width: 3,
    height: 3
}

在标准库里,我们也经常看到这种关联函数,比如 String::from("Hello")。它不需要某个已存在的 String 实例,就可以直接调用,用来创建一个新的字符串。


7. 多个 impl 块

你可以为同一个类型写多个 impl 块,比如:

rust 复制代码
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

这与把它们写在同一个 impl 中没有本质差别。之所以 Rust 允许你分开写,是为了在某些情况下(比如涉及到泛型、trait 实现等)组织代码更灵活。

8. 总结

  • 方法 :必须定义在某个类型(如 struct)的 impl 块中,第一个参数是 self(可变或不可变)。方法往往用于描述该类型实例的某些行为,读或写其内部数据。
  • 关联函数 :在 impl 块里定义但不包含 self 参数的函数。常用于构造新实例或提供一些与实例无关的功能。
  • Rust 拥有自动引用和解引用 特性,让我们可以简洁地使用 object.method() 来调用方法。
  • 多个 impl 块可以并存,给代码的组织提供了很大灵活性。

通过为自定义类型定义方法,我们不仅能让代码更具可读性,把相关的行为放到同一个 impl 块中,也能充分利用所有权、借用等特性来保证内存安全和并发安全。希望这篇文章能帮你搞清楚在 Rust 中如何编写方法、何时使用 &self&mut selfself,以及如何借助关联函数让代码更简洁优雅。

如果你还对 Rust 中的枚举(enum)或 trait 有兴趣,不妨继续阅读之后的章节,它们和 struct 一样,也是构建复杂逻辑的重要工具。

相关推荐
rain bye bye13 小时前
calibre LVS 跑不起来 就将setup 的LVS Option connect下的 connect all nets by name 打开。
服务器·数据库·lvs
郭式云源生法则13 小时前
归档及压缩、重定向与管道操作和综合使用,find精确查找、find处理查找结果、vim高级使用、vimdiff多文件使用
linux·运维·服务器
R-G-B13 小时前
【02】C#入门到精通——C# 变量、输入/输出、类型转换
开发语言·c#·c# 变量·c#输入/输出·c#类型转换
星河队长13 小时前
C# 软件加密方法,有使用时间限制,同时要防止拷贝
开发语言·c#
史迪奇_xxx13 小时前
10、一个简易 vector:C++ 模板与 STL
java·开发语言·c++
2301_8012522213 小时前
Java中的反射
java·开发语言
muyouking1114 小时前
Tauri Android 开发踩坑实录:从 Gradle 版本冲突到离线构建成功
android·rust
Kiri霧14 小时前
Rust开发环境搭建
开发语言·后端·rust
小池先生14 小时前
服务请求出现偶发超时问题,经查服务本身没问题,问题出现在nginx转发。
运维·服务器·nginx
weixin-a1530030831614 小时前
[数据抓取-1]beautifulsoup
开发语言·python·beautifulsoup