Introdução: Uma letra minúscula, uma grande diferença
\n\nOlá, futuro desenvolvedor Rust! Se você já começou a estudar Rust, certamente notou que o compilador é um professor rigoroso, mas justo. Ele não perdoa erros de digitação, muito menos confusão na sintaxe. Uma das armadilhas mais comuns em que iniciantes (e não só) caem é a confusão entre self e Self. Aparentemente, a diferença é apenas uma letra — maiúscula ou minúscula. Mas por trás dessa letra esconde-se uma diferença fundamental entre a instância e o tipo.
No Stack Overflow, essa pergunta tem milhares de visualizações, e não é por acaso. Hoje vamos entender de uma vez por todas a diferença entre self e Self em Rust. Não vamos apenas ler uma definição chata, mas sim seguir um tutorial passo a passo com exemplos práticos, escrevendo alguns pequenos programas. Ao final do artigo, você não apenas saberá a diferença, mas a sentirá em um nível intuitivo. Vamos lá!
1. self: "Eu" — é sobre a instância
\n\nVamos começar com self. Em Rust, como em muitas outras linguagens, self é usado para se referir à instância atual de uma struct ou enumeração. É o análogo de this em C++, Java, JavaScript ou Python. Quando você escreve um método que é chamado em um objeto específico, você usa self.
Existem três formas principais de self na assinatura do método:
- \n
self— assume a posse da instância (consome a instância). Após a chamada do método, o objeto original fica inacessível. \n &self— uma referência imutável à instância (o caso mais comum). O método pode ler os dados, mas não pode alterá-los. \n &mut self— uma referência mutável à instância. O método pode alterar os campos da struct. \n
Vamos escrever um exemplo simples. Imagine que estamos criando uma struct Estudante.
struct Estudante {\n nome: String,\n curso: u32,\n}\n\nimpl Estudante {\n // Método que recebe &self (referência imutável)\n fn apresentar(&self) {\n println!("Olá! Eu sou {}, estou no {}º ano.", self.nome, self.curso);\n }\n\n // Método que recebe &mut self (referência mutável)\n fn avancar_para_proximo_ano(&mut self) {\n self.curso += 1;\n println!("{} agora está no {}º ano!", self.nome, self.curso);\n }\n\n // Método que recebe self (posse)\n fn concluir_estudos(self) {\n println!("{} concluiu os estudos no {}º ano. Adeus!", self.nome, self.curso);\n // self é destruído aqui, a memória é liberada\n }\n}\n\nfn main() {\n let mut estudante = Estudante {\n nome: String::from("Ana"),\n curso: 2,\n };\n\n estudante.apresentar(); // &self\n estudante.avancar_para_proximo_ano(); // &mut self\n estudante.concluir_estudos(); // self (posse)\n\n // Após chamar concluir_estudos, a variável 'estudante' não está mais disponível!\n // Descomente a linha a seguir para ver o erro de compilação:\n // estudante.apresentar(); // ERRO: borrow of moved value\n}\n\nObserve o último comentário. Quando passamos self (sem referência), estamos transferindo a posse. Esta é uma ferramenta poderosa que permite controlar precisamente o tempo de vida do objeto. Em outras linguagens (por exemplo, Python), não há esse controle — o objeto vive enquanto houver referências a ele. Em Rust, você decide quando o objeto será "consumido".
2. Self: "Eu" — é sobre o tipo
\n\nAgora a parte mais interessante. Self (com letra maiúscula) é um apelido (alias) para o tipo para o qual estamos implementando métodos. Simplificando, Self dentro de um bloco impl é substituído pelo nome da struct ou enumeração com a qual estamos trabalhando.
Por que isso é necessário? Primeiro, é conveniente. Em vez de escrever o nome completo da struct, você escreve Self. Segundo, é crucial