Rust中的方法与关联函数使用解读

 更新时间:2025年02月26日 15:34:24   作者:Hello.Reader  
在Rust中,方法是定义在特定类型(如struct)的impl块中,第一个参数是self(可变或不可变),方法用于描述该类型实例的行为,而关联函数则不包含self参数,常用于构造新实例或提供一些与实例无关的功能,Rust的自动引用和解引用特性使得方法调用更加简洁

1. 方法(Methods)是什么?

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

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

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

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

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

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

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

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

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

运行后,会输出:

rect1 的面积是:1500

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

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

fn area(rectangle: &Rectangle) -> u32 { ... }

变为

fn area(&self) -> u32 { ... }

这是因为在 impl Rectangle 这个上下文中,Rust 给出了一个更具可读性的方式:让第一个参数自动变为 self,而 Self 则是当前实现块对应的类型别名。

如果你需要修改实例的字段,你可以将第一个参数写为 &mut self;如果需要获取所有权并可能在方法内部把它“转化”成别的东西,则用 self。但这种把所有权转移给方法本身的做法很少见。

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

3. 同名字段与同名方法

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

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()

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

p1.distance(&p2);
(&p1).distance(&p2);

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

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

方法和函数在参数上并没什么区别,除了第一个参数是 self 以外,其他参数你可以自由添加。

举例来说,为 Rectangle 再定义一个方法 can_hold,用来检查“当前矩形”是否可以完全容纳另一个矩形:

impl Rectangle {
    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 };
    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)

关联函数常被用来提供类似“构造函数”的功能。

举个例子,如果你想快速构造一个“正方形”:

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

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

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

打印结果为:

正方形 sq: Rectangle {
    width: 3,
    height: 3
}

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

7. 多个 impl 块

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

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 一样,也是构建复杂逻辑的重要工具。

当然,以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 如何使用Rust直接编译单个的Solidity合约

    如何使用Rust直接编译单个的Solidity合约

    本文介绍了如何使用Rust语言直接编译Solidity智能合约,特别适用于没有外部依赖或flatten后的合约,一般情况下,Solidity开发者使用Hardhat或Foundry框架,本文给大家介绍如何使用Rust直接编译单个的Solidity合约,感兴趣的朋友一起看看吧
    2024-09-09
  • 使用cargo install安装Rust二进制工具过程

    使用cargo install安装Rust二进制工具过程

    cargoinstall是一个用于安装包含可执行目标的Rust包的命令行工具,类似于系统软件包管理器,但它为Rust开发者提供了一种简洁的方式来安装和管理命令行工具,安装后,二进制文件会存储在$HOME/.cargo/bin目录中,需要将该目录添加到$PATH环境变量中才能在命令行中直接运行
    2025-02-02
  • Rust实现一个表达式Parser小结

    Rust实现一个表达式Parser小结

    这篇文章主要为大家介绍了Rust实现一个表达式Parser小结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • 从零开始使用Rust编写nginx(TLS证书快过期了)

    从零开始使用Rust编写nginx(TLS证书快过期了)

    wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,本文给大家介绍从零开始使用Rust编写nginx(TLS证书快过期了),感兴趣的朋友一起看看吧
    2024-03-03
  • 深入理解Rust中Cargo的使用

    深入理解Rust中Cargo的使用

    本文主要介绍了深入理解Rust中Cargo的使用,Cargo简化了项目的构建过程,提供了依赖项管理,以及一系列方便的工作流程工具,下面就来具体的介绍一下如何使用,感兴趣的可以了解一下
    2024-04-04
  • rust语言基础pub关键字及Some语法示例

    rust语言基础pub关键字及Some语法示例

    这篇文章主要为大家介绍了rust语言基础pub关键字及Some语法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • Rust的slab库使用场景分析

    Rust的slab库使用场景分析

    slab 是一个轻量级、高性能的工具,非常适合管理固定大小的资源集合,尤其是在网络编程和事件驱动架构中,这篇文章主要介绍了Rust的slab库使用教程,需要的朋友可以参考下
    2024-12-12
  • Rust捕获全局panic并记录进程退出日志的方法

    Rust捕获全局panic并记录进程退出日志的方法

    本文提供了捕获全局panic并记录进程退出日志的方法,首先使用 panic::set_hook 注册异常处理及panic 触发异常,结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-04-04
  • rust声明式宏的实现

    rust声明式宏的实现

    声明式宏使得你能够写出类似 match 表达式的东西,来操作你所提供的 Rust代码,它使用你提供的代码来生成用于替换宏调用的代码,感兴趣的可以了解一下
    2023-12-12
  • Rust实现构建器模式和如何使用Bon库中的构建器

    Rust实现构建器模式和如何使用Bon库中的构建器

    这篇文章主要介绍了Rust实现构建器模式和如何使用Bon库中的构建器,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-08-08

最新评论