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 &self sempre que o método não precisar modificar o estado.
  • Use &mut self apenas quando necessário para mutação.
  • Evite self (consumir) a menos que você queira impedir o uso posterior da instância.
  • Nomeie construtores como new por 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

Exercícios

  1. Crie uma struct Livro com campos titulo (String), autor (String) e paginas (u32). Implemente um método resumo que retorna uma string formatada como " por " e um método paginas_restantes que 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 }
        }
    }
  2. Implemente um método aumentar_preco para uma struct Produto com campo preco (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);
        }
    }
  3. Crie um método associado criar_novo para a struct Pessoa (campos nome e idade) que recebe nome e idade e retorna uma instância. Em seguida, implemente um método de instância aniversario que 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;
        }
    }
  4. Escreva um método que consuma a instância (self) para uma struct ContaBancaria com campo saldo (f64). O método fechar_conta deve retornar o saldo final e impedir o uso posterior.

    ✓ Resposta:
    struct ContaBancaria {
        saldo: f64,
    }
    
    impl ContaBancaria {
        fn fechar_conta(self) -> f64 {
            self.saldo
        }
    }
  5. Implemente um construtor new para uma struct Circulo com campo raio (f64) e um método area que calcula a área. Use &self e a constante std::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
        }
    }