Introduction: One Small Letter, a Big Difference
\n\nHello, future Rust developer! If you've already started learning Rust, you've probably noticed that the compiler is a strict but fair teacher. It doesn't forgive typos, let alone confusion in syntax. One of the most common pitfalls for beginners (and not only) is confusing self and Self. It seems like the difference is just one letter — uppercase or lowercase. But behind this letter lies a fundamental difference between an instance and a type.
On Stack Overflow, this question has garnered thousands of views, and for good reason. Today, we will once and for all understand the difference between self and Self in Rust. We won't just read a boring definition; we'll go through a step-by-step tutorial with live examples, writing a couple of small programs. By the end of the article, you won't just know the difference — you'll feel it on an intuitive level. Let's go!
1. self: "I" — Refers to the Instance
\n\nLet's start with self. In Rust, as in many other languages, self is used to denote the current instance of a struct or enum. It's the equivalent of this in C++, Java, JavaScript, or Python. When you write a method that is called on a specific object, you use self.
self has three main forms in a method signature:
- \n
self— takes ownership of the instance (consumes the instance). After the method call, the original object becomes unavailable. \n &self— an immutable reference to the instance (the most common case). The method can read data but cannot change it. \n &mut self— a mutable reference to the instance. The method can modify the struct's fields. \n
Let's write a simple example. Imagine we are creating a Student struct.
struct Student {\n name: String,\n year: u32,\n}\n\nimpl Student {\n // Method that takes &self (immutable reference)\n fn introduce(&self) {\n println!("Hi! I am {}, studying in year {}.", self.name, self.year);\n }\n\n // Method that takes &mut self (mutable reference)\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 // Method that takes self (ownership)\n fn graduate(self) {\n println!("{} has graduated in year {}. Goodbye!", self.name, self.year);\n // self is destroyed here, memory is freed\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 (ownership)\n\n // After calling graduate, the variable 'student' is no longer accessible!\n // Uncomment the following line to see the compilation error:\n // student.introduce(); // ERROR: borrow of moved value\n}\n\nNotice the last comment. When we pass self (without a reference), we give up ownership. This is a powerful tool that allows precise control over an object's lifetime. In other languages (e.g., Python), there is no such control — the object lives as long as there are references to it. In Rust, you decide when an object is "consumed."
2. Self: "I" — Refers to the Type
\n\nNow for the interesting part. Self (with a capital S) is a type alias for the type for which we are implementing methods. Simply put, Self inside an impl block is replaced by the name of the struct or enum we are working with.
Why is this needed? First, it's convenient. Instead of writing the full struct name, you write Self. Second, it is critical