引言:一个小字母,天壤之别
\n\n你好,未来的 Rust 开发者!如果你已经开始学习 Rust,可能已经注意到编译器是一位严格但公正的老师。它不会容忍拼写错误,更不用说语法混淆了。对于初学者(以及不仅仅是初学者)来说,最常见的陷阱之一就是混淆 self 和 Self。看起来区别只是一个大写字母的大小写而已。但在这个字母背后,隐藏着实例和类型之间的根本区别。
在 Stack Overflow 上,这个问题已经收获了数千次浏览,这并非没有原因。今天,我们将一劳永逸地理解 Rust 中 self 和 Self 的区别。我们不会只是阅读枯燥的定义;我们将通过实际示例逐步学习,编写几个小程序。到文章结束时,你不仅会知道它们的区别,还会在直觉上感受到它。我们开始吧!
1. self:“我”——指的是实例
\n\n让我们从 self 开始。在 Rust 中,就像在许多其他语言中一样,self 用于表示结构体或枚举的当前实例。它相当于 C++、Java、JavaScript 或 Python 中的 this。当你编写一个在特定对象上调用的方法时,你会使用 self。
在方法签名中,self 有三种主要形式:
- \n
self— 获取实例的所有权(消耗实例)。方法调用后,原始对象将不可用。 \n &self— 对实例的不可变引用(最常见的情况)。方法可以读取数据但不能更改它。 \n &mut self— 对实例的可变引用。方法可以修改结构体的字段。 \n
让我们写一个简单的示例。假设我们正在创建一个 Student 结构体。
struct Student {\n name: String,\n year: u32,\n}\n\nimpl Student {\n // 接受 &self(不可变引用)的方法\n fn introduce(&self) {\n println!("Hi! I am {}, studying in year {}.", self.name, self.year);\n }\n\n // 接受 &mut self(可变引用)的方法\n fn advance_to_next_year(&mut self) {\n self.year += 1;\n println!("{} is now in year {}!", self.name, self.year);\n }\n\n // 接受 self(所有权)的方法\n fn graduate(self) {\n println!("{} has graduated in year {}. Goodbye!", self.name, self.year);\n // self 在这里被销毁,内存被释放\n }\n}\n\nfn main() {\n let mut student = Student {\n name: String::from("Anna"),\n year: 2,\n };\n\n student.introduce(); // &self\n student.advance_to_next_year(); // &mut self\n student.graduate(); // self(所有权)\n\n // 在调用 graduate 之后,变量 'student' 不再可访问!\n // 取消下面一行的注释,看看编译错误:\n // student.introduce(); // 错误:移动值的借用\n}\n\n注意最后的注释。当我们传递 self(不带引用)时,我们放弃了所有权。这是一个强大的工具,可以精确控制对象的生命周期。在其他语言(例如 Python)中,没有这样的控制——只要存在引用,对象就会一直存活。在 Rust 中,你决定对象何时被“消耗”。
2. Self:“我”——指的是类型
\n\n现在是有趣的部分。Self(大写的 S)是我们正在为其实现方法的类型的类型别名。简单来说,在 impl 块内部,Self 被替换为我们正在处理的结构体或枚举的名称。
为什么需要这个?首先,它很方便。你不需要写完整的结构体名称,而是写 Self。其次,它在某些情况下至关重要,例如在 trait 定义中,或者当方法返回当前类型时。