引言:一个小字母,大不同
你好,未来的Rust开发者!如果你已经开始学习Rust,你肯定已经注意到,编译器是一位严格但公正的老师。它不会原谅拼写错误,更不用说语法上的混淆了。新手(甚至不仅仅是新手)最容易掉入的陷阱之一,就是混淆self和Self。看起来,区别仅仅在于一个字母——大写或小写。但在这个字母背后,隐藏着实例和类型之间的根本区别。
在Stack Overflow上,这个问题获得了数千次浏览,这并非偶然。今天,我们将一劳永逸地弄清楚Rust中self和Self的区别。我们不会只是阅读枯燥的定义,而是通过编写几个小程序,进行一个包含实际示例的逐步教程。到文章结束时,你不仅会知道区别,更会从直觉上感受到它。我们开始吧!
1. self:“我”指的是实例
我们从self开始。在Rust中,与许多其他语言一样,self用于表示结构体或枚举的当前实例。它类似于C++、Java、JavaScript或Python中的this。当你编写一个在特定对象上调用的方法时,你会使用self。
在方法签名中,self有三种主要形式:
self— 获取实例的所有权(consumes the instance)。调用方法后,原始对象将变得不可用。&self— 对实例的不可变引用(最常见的情况)。方法可以读取数据,但不能修改它们。&mut self— 对实例的可变引用。方法可以修改结构体的字段。
让我们写一个简单的例子。假设我们创建一个Студент结构体。
struct Студент { имя: String, курс: u32,}
impl Студент { // 获取 &self(不可变引用)的方法 fn представиться(&self) { println!("Привет! Я {}, учусь на {} курсе.", self.имя, self.курс); }
// 获取 &mut self(可变引用)的方法 fn перейти_на_следующий_курс(&mut self) { self.курс += 1; println!("{} теперь на {} курсе!", self.имя, self.курс); }
// 获取 self(所有权)的方法 fn закончить_обучение(self) { println!("{} закончил обучение на {} курсе. Прощай!", self.имя, self.курс); // 这里的 self 被销毁,内存被释放 }}
fn main() { let mut студент = Студент { имя: String::from("Анна"), курс: 2, };
студент.представиться(); // &self студент.перейти_на_следующий_курс(); // &mut self студент.закончить_обучение(); // self (所有权)
// 调用 закончить_обучение 后,变量 'студент' 不再可用! // 取消注释下一行以查看编译错误: // студент.представиться(); // 错误:使用了已移动的值}请注意最后的注释。当我们传递self(不带引用)时,我们交出了所有权。这是一个强大的工具,它允许你精确控制对象的生命周期。在其他语言(例如Python)中,没有这种控制——只要对象有引用,它就存在。在Rust中,你自己决定对象何时被“消费”。
2. Self:“我”指的是类型
现在到了最有趣的部分。Self(大写)是我们为其实现方法的类型的别名。简单来说,在impl块内部,Self会被替换为我们正在处理的结构体或枚举的名称。
为什么需要这个?首先,它很方便。你不需要写出完整的结构体名称,只需写Self。其次,它至关重