17人参与 • 2025-02-26 • rust
在 rust 里,方法和函数的定义方式很像:
fn
来声明。不同点在于: 方法必须定义在某个具体类型(比如 struct
、enum
或者在某个 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.width
和 self.height
即代表该实例的字段。用 self
访问字段非常直观。在 main
函数中,当我们创建一个矩形实例后,就可以使用方法语法来获取面积:
fn main() { let rect1 = rectangle { width: 30, height: 50, }; println!("rect1 的面积是:{}", rect1.area()); }
运行后,会输出:
rect1 的面积是:1500
在我们将 area
从一个普通函数重构为一个方法时,你会注意到,函数签名由原本的
fn area(rectangle: &rectangle) -> u32 { ... }
变为
fn area(&self) -> u32 { ... }
这是因为在 impl rectangle
这个上下文中,rust 给出了一个更具可读性的方式:让第一个参数自动变为 self
,而 self
则是当前实现块对应的类型别名。
如果你需要修改实例的字段,你可以将第一个参数写为 &mut self
;如果需要获取所有权并可能在方法内部把它“转化”成别的东西,则用 self
。但这种把所有权转移给方法本身的做法很少见。
在大多数情况下,我们只是想读一下结构体数据而不改变它,这时使用 &self
最为常见,也能让调用者继续使用这个实例。
如果你在 rectangle
内也有一个字段叫做 width
,同时还想定义一个方法也叫 width
,这是合法的。比如:
impl rectangle { fn width(&self) -> bool { self.width > 0 } }
在调用时:
rect.width
(不带括号)访问的是字段 width
的数值。rect.width()
(带括号)调用的是同名方法,返回一个布尔值。在很多语言中,如果你只想单纯地返回字段值,会把这种方法称为“getter”。
rust 并不会为你自动生成 getter,但你可以自行定义。
这样一来,你可以只把字段设为私有,但对外公开这个只读方法,让外部安全地访问它。
在 c/c++ 中,如果你要通过指针来调用成员函数,需要写 ->
。或者,如果你手头是指针,还要显式地 (*object).method()
等。
在 rust 中则不需要这么麻烦,因为自动引用和解引用让你可以直接写 object.method()
。
实际上,这些调用是一样的:
p1.distance(&p2); (&p1).distance(&p2);
rust 会根据方法签名(第一个参数是 &self
、&mut self
还是 self
)来自动推断是否需要帮你加 &
、&mut
或者 *
。这大大简化了调用方法时的语法。
方法和函数在参数上并没什么区别,除了第一个参数是 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 }
如果在 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
实例,就可以直接调用,用来创建一个新的字符串。
你可以为同一个类型写多个 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 实现等)组织代码更灵活。
struct
)的 impl
块中,第一个参数是 self
(可变或不可变)。方法往往用于描述该类型实例的某些行为,读或写其内部数据。impl
块里定义但不包含 self
参数的函数。常用于构造新实例或提供一些与实例无关的功能。object.method()
来调用方法。impl
块可以并存,给代码的组织提供了很大灵活性。通过为自定义类型定义方法,我们不仅能让代码更具可读性,把相关的行为放到同一个 impl
块中,也能充分利用所有权、借用等特性来保证内存安全和并发安全。
希望这篇文章能帮你搞清楚在 rust 中如何编写方法、何时使用 &self
、&mut self
、self
,以及如何借助关联函数让代码更简洁优雅。
如果你还对 rust 中的枚举(enum)或 trait 有兴趣,不妨继续阅读之后的章节,它们和 struct
一样,也是构建复杂逻辑的重要工具。
当然,以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论