Métodos e blocos impl
Nesta aula, exploramos como definir métodos em Rust usando blocos `impl`, os diferentes tipos de self (&self, &mut self, self), métodos associados e o padrão de construtores como `new`.
Em Rust, métodos são funções associadas a um tipo (struct, enum ou trait) e são definidos dentro de um bloco impl. Eles permitem que você organize a lógica que opera em dados de forma encapsulada, similar a métodos em outras linguagens, mas com diferenças importantes relacionadas à posse e à mutabilidade. Nesta aula, você aprenderá a criar e usar métodos, entenderá os diferentes tipos de self e como implementar construtores.
Os blocos impl são fundamentais para a programação orientada a objetos em Rust, embora Rust não seja uma linguagem puramente OO. Eles permitem associar comportamento a tipos de forma clara e eficiente, aproveitando o sistema de tipos e o modelo de propriedade.
impl
O bloco impl (abreviação de "implementation") é usado para definir métodos para um tipo específico. Ele pode conter tanto métodos que recebem self (métodos de instância) quanto funções associadas que não recebem self (como construtores).
A sintaxe básica é:
struct NomeStruct {
campo: Tipo,
}
impl NomeStruct {
fn metodo(&self) {
// corpo
}
}Exemplo prático:
struct Retangulo {
largura: u32,
altura: u32,
}
impl Retangulo {
fn area(&self) -> u32 {
self.largura * self.altura
}
}
fn main() {
let ret = Retangulo { largura: 10, altura: 5 };
println!("Área: {}", ret.area());
}self, &self, &mut self
O primeiro parâmetro de um método é sempre self, que representa a instância do tipo. Existem três variantes:
self: toma posse da instância (consome o valor). Raramente usado, útil para transformar ou mover o valor.&self: empresta a instância de forma imutável. Permite ler os dados sem modificá-los.&mut self: empresta a instância de forma mutável. Permite modificar os dados.
Exemplo:
struct Contador {
valor: i32,
}
impl Contador {
fn novo(valor_inicial: i32) -> Self {
Self { valor: valor_inicial }
}
fn ler(&self) -> i32 {
self.valor
}
fn incrementar(&mut self) {
self.valor += 1;
}
fn consumir(self) -> i32 {
self.valor
}
}
fn main() {
let mut c = Contador::novo(0);
c.incrementar();
println!("Valor: {}", c.ler());
let v = c.consumir(); // c não pode mais ser usado
println!("Consumido: {}", v);
}Métodos associados
Métodos associados são funções definidas dentro de um bloco impl que não recebem self como parâmetro. São chamadas usando a sintaxe Tipo::metodo(). Eles são frequentemente usados como construtores ou funções utilitárias.
Exemplo:
struct Ponto {
x: f64,
y: f64,
}
impl Ponto {
fn distancia_origem(x: f64, y: f64) -> f64 {
(x * x + y * y).sqrt()
}
}
fn main() {
let d = Ponto::distancia_origem(3.0, 4.0);
println!("Distância: {}", d);
}Construtores (new)
Por convenção, construtores em Rust são funções associadas chamadas new que criam e retornam uma nova instância do tipo. Não há sobrecarga de construtores como em outras linguagens; você pode criar funções associadas com nomes diferentes.
Exemplo:
struct Usuario {
nome: String,
idade: u8,
}
impl Usuario {
fn new(nome: String, idade: u8) -> Self {
Self { nome, idade }
}
}
fn main() {
let user = Usuario::new(String::from("Alice"), 30);
println!("{} tem {} anos", user.nome, user.idade);
}Observe que Self é um alias para o tipo do bloco impl. Você também pode usar o nome do tipo diretamente.
Boas práticas
- Prefira
&selfsempre que o método não precisar modificar o estado. - Use
&mut selfapenas quando necessário para mutação. - Evite
self(consumir) a menos que você queira impedir o uso posterior da instância. - Nomeie construtores como
newpor convenção, mas crie outros nomes descritivos quando houver múltiplas formas de construção. - Mantenha métodos curtos e focados em uma única responsabilidade.
Referências
- The Rust Programming Language - Method Syntax
- Rust by Example - Methods
- Rust Keyword impl
- Rust Keyword self
- Rust Keyword Self
- Rust Reference - Implementations
Exercícios
Crie uma struct
Livrocom campostitulo(String),autor(String) epaginas(u32). Implemente um métodoresumoque retorna uma string formatada como "por " e um método paginas_restantesque recebe um número de páginas lidas e retorna quantas faltam. Use&self.✓ Resposta:struct Livro { titulo: String, autor: String, paginas: u32, } impl Livro { fn resumo(&self) -> String { format!("{} por {}", self.titulo, self.autor) } fn paginas_restantes(&self, lidas: u32) -> u32 { if lidas >= self.paginas { 0 } else { self.paginas - lidas } } }Implemente um método
aumentar_precopara uma structProdutocom campopreco(f64) que recebe um percentual e aumenta o preço. Use&mut self.✓ Resposta:struct Produto { preco: f64, } impl Produto { fn aumentar_preco(&mut self, percentual: f64) { self.preco += self.preco * (percentual / 100.0); } }Crie um método associado
criar_novopara a structPessoa(camposnomeeidade) que recebe nome e idade e retorna uma instância. Em seguida, implemente um método de instânciaaniversarioque incrementa a idade. Use&mut self.✓ Resposta:struct Pessoa { nome: String, idade: u32, } impl Pessoa { fn criar_novo(nome: String, idade: u32) -> Self { Self { nome, idade } } fn aniversario(&mut self) { self.idade += 1; } }Escreva um método que consuma a instância (
self) para uma structContaBancariacom camposaldo(f64). O métodofechar_contadeve retornar o saldo final e impedir o uso posterior.✓ Resposta:struct ContaBancaria { saldo: f64, } impl ContaBancaria { fn fechar_conta(self) -> f64 { self.saldo } }Implemente um construtor
newpara uma structCirculocom camporaio(f64) e um métodoareaque calcula a área. Use&selfe a constantestd::f64::consts::PI.✓ Resposta:struct Circulo { raio: f64, } impl Circulo { fn new(raio: f64) -> Self { Self { raio } } fn area(&self) -> f64 { std::f64::consts::PI * self.raio * self.raio } }