Introducción: Una pequeña letra, una gran diferencia
\n\n¡Hola, futuro desarrollador Rust! Si ya has comenzado a estudiar Rust, seguramente habrás notado que el compilador es un maestro estricto pero justo. No perdona errores tipográficos y mucho menos confusiones en la sintaxis. Una de las trampas más comunes en las que caen los principiantes (y no solo ellos) es la confusión entre self y Self. Parece que la diferencia es solo una letra: mayúscula o minúscula. Pero detrás de esa letra se esconde una diferencia fundamental entre la instancia y el tipo.
En Stack Overflow, esta pregunta ha acumulado miles de visitas, y no es casualidad. Hoy vamos a aclarar de una vez por todas en qué se diferencian self y Self en Rust. No solo leeremos una definición aburrida, sino que seguiremos un tutorial paso a paso con ejemplos prácticos, escribiendo un par de programas pequeños. Al final del artículo, no solo conocerás la diferencia, sino que la sentirás a nivel intuitivo. ¡Allá vamos!
1. self: «Yo» se refiere a la instancia
\n\nEmpecemos con self. En Rust, como en muchos otros lenguajes, self se utiliza para referirse a la instancia actual de una estructura o enumeración. Es el equivalente de this en C++, Java, JavaScript o Python. Cuando escribes un método que se invoca en un objeto específico, usas self.
self tiene tres formas principales en la firma del método:
- \n
self— toma posesión de la instancia (consume la instancia). Después de llamar al método, el objeto original deja de estar disponible. \n &self— una referencia inmutable a la instancia (el caso más común). El método puede leer datos, pero no puede modificarlos. \n &mut self— una referencia mutable a la instancia. El método puede modificar los campos de la estructura. \n
Escribamos un ejemplo simple. Imagina que creamos una estructura Estudiante.
struct Estudiante {\n nombre: String,\n curso: u32,\n}\n\nimpl Estudiante {\n // Método que toma &self (referencia inmutable)\n fn presentarse(&self) {\n println!("¡Hola! Soy {}, estudio en el {} curso.", self.nombre, self.curso);\n }\n\n // Método que toma &mut self (referencia mutable)\n fn pasar_al_siguiente_curso(&mut self) {\n self.curso += 1;\n println!("¡{} ahora está en el {} curso!", self.nombre, self.curso);\n }\n\n // Método que toma self (posesión)\n fn graduarse(self) {\n println!("{} se graduó en el {} curso. ¡Adiós!", self.nombre, self.curso);\n // self se destruye aquí, la memoria se libera\n }\n}\n\nfn main() {\n let mut estudiante = Estudiante {\n nombre: String::from("Ana"),\n curso: 2,\n };\n\n estudiante.presentarse(); // &self\n estudiante.pasar_al_siguiente_curso(); // &mut self\n estudiante.graduarse(); // self (posesión)\n\n // Después de llamar a graduarse, ¡la variable 'estudiante' ya no está disponible!\n // Descomenta la siguiente línea para ver el error de compilación:\n // estudiante.presentarse(); // ERROR: borrow of moved value\n}\n\nObserva el último comentario. Cuando pasamos self (sin referencia), cedemos la propiedad. Esta es una herramienta poderosa que permite controlar con precisión el ciclo de vida del objeto. En otros lenguajes (por ejemplo, Python), no existe este control: el objeto vive mientras haya referencias a él. En Rust, tú decides cuándo el objeto será «consumido».
2. Self: «Yo» se refiere al tipo
\n\nY ahora lo más interesante. Self (con mayúscula) es un alias para el tipo para el cual estamos implementando métodos. En términos simples, Self dentro del bloque impl se reemplaza por el nombre de la estructura o enumeración con la que estamos trabajando.
¿Para qué sirve esto? En primer lugar, es conveniente. En lugar de escribir el nombre completo de la estructura, escribes Self. En segundo lugar, es crític