Métodos: funções associadas a tipos
1. O que são Métodos em Go
Em Go, um método é uma função especial que possui um receiver — um parâmetro adicional que vincula a função a um tipo específico. Enquanto funções comuns operam independentemente, métodos definem comportamentos associados diretamente a dados.
A sintaxe básica de um método é:
func (t Tipo) NomeDoMetodo(parametros) TipoRetorno {
// corpo do método
}
A diferença fundamental entre função e método é que o método só pode ser chamado através de uma instância do tipo ao qual está associado. Vamos ver um exemplo prático:
package main
import "fmt"
type Retangulo struct {
Largura, Altura float64
}
// Método Area() associado ao tipo Retangulo
func (r Retangulo) Area() float64 {
return r.Largura * r.Altura
}
func main() {
ret := Retangulo{Largura: 10, Altura: 5}
fmt.Println("Área do retângulo:", ret.Area()) // 50
}
Aqui, Area() é um método porque possui o receiver (r Retangulo). Diferente de uma função, não precisamos passar o retângulo como argumento — chamamos diretamente ret.Area().
2. Declarando Métodos para Tipos Customizados
Métodos podem ser declarados para qualquer tipo definido no mesmo pacote, incluindo structs e tipos base.
type Nome string
func (n Nome) ToUpper() string {
return strings.ToUpper(string(n))
}
type Pessoa struct {
Nome string
Idade int
}
func (p Pessoa) Saudacao() string {
return "Olá, eu sou " + p.Nome
}
Restrição importante: métodos só podem ser declarados no mesmo pacote do tipo. Não é possível adicionar métodos a tipos de pacotes externos.
3. Receivers por Valor vs por Ponteiro
Go oferece duas formas de receiver: por valor e por ponteiro. A escolha impacta diretamente se o método pode modificar o receptor original.
type Conta struct {
Saldo float64
}
// Receiver por valor: não modifica o original
func (c Conta) ExibirSaldo() float64 {
return c.Saldo
}
// Receiver por ponteiro: permite modificar o original
func (c *Conta) Depositar(valor float64) {
c.Saldo += valor
}
func main() {
conta := Conta{Saldo: 1000}
conta.Depositar(500)
fmt.Println(conta.ExibirSaldo()) // 1500
}
Go resolve automaticamente chamadas: se você tem um valor e o método espera ponteiro (ou vice-versa), o compilador faz a conversão implícita. No entanto, a regra prática é: use ponteiro quando precisar modificar o receiver ou quando a struct for grande (evita cópia), e use valor para tipos pequenos e imutáveis.
4. Métodos com Ponteiros e Nil
É possível chamar métodos em ponteiros nil, mas isso requer cuidado para evitar panic:
type Lista struct {
Valor int
Prox *Lista
}
func (l *Lista) Listar() {
if l == nil {
fmt.Println("Lista vazia")
return
}
atual := l
for atual != nil {
fmt.Println(atual.Valor)
atual = atual.Prox
}
}
func main() {
var lista *Lista // nil
lista.Listar() // "Lista vazia" — seguro
}
A boa prática é sempre verificar if t == nil em métodos que podem receber nil, documentando esse comportamento.
5. Encadeamento de Métodos (Method Chaining)
Uma técnica poderosa é retornar o próprio receiver para permitir chamadas encadeadas, criando uma fluent interface:
type PessoaBuilder struct {
Nome string
Idade int
}
func (p *PessoaBuilder) SetNome(nome string) *PessoaBuilder {
p.Nome = nome
return p
}
func (p *PessoaBuilder) SetIdade(idade int) *PessoaBuilder {
p.Idade = idade
return p
}
func (p *PessoaBuilder) Build() Pessoa {
return Pessoa{Nome: p.Nome, Idade: p.Idade}
}
// Uso:
pessoa := new(PessoaBuilder).SetNome("João").SetIdade(30).Build()
Cuidado: se o receiver for por valor, o encadeamento não funcionará corretamente, pois cada chamada opera em uma cópia diferente.
6. Métodos e Interfaces
Em Go, interfaces são implementadas implicitamente. Um tipo implementa uma interface se possuir todos os métodos exigidos por ela:
type Stringer interface {
String() string
}
type Pessoa struct {
Nome string
}
func (p Pessoa) String() string {
return "Pessoa: " + p.Nome
}
func main() {
var s Stringer = Pessoa{"Maria"}
fmt.Println(s.String()) // "Pessoa: Maria"
}
Interfaces exigem métodos, não campos. Isso permite polimorfismo e desacoplamento. Type assertion e type switch permitem verificar o tipo concreto por trás da interface:
switch v := s.(type) {
case Pessoa:
fmt.Println("É uma pessoa:", v.Nome)
default:
fmt.Println("Tipo desconhecido")
}
7. Métodos em Tipos Embutidos (Embedding)
Go não tem herança clássica, mas oferece embedding — um mecanismo onde um struct "herda" métodos de outro:
type Animal struct {
Nome string
}
func (a Animal) Falar() string {
return "Som genérico"
}
type Cachorro struct {
Animal // embedding
Raca string
}
// Sobrescrita (shadowing) do método Falar
func (c Cachorro) Falar() string {
return "Au au!"
}
func main() {
c := Cachorro{
Animal: Animal{Nome: "Rex"},
Raca: "Labrador",
}
fmt.Println(c.Falar()) // "Au au!" — método sobrescrito
fmt.Println(c.Animal.Falar()) // "Som genérico" — acesso direto
}
Métodos do tipo embutido são promovidos automaticamente, permitindo acesso direto sem qualificação.
8. Boas Práticas e Armadilhas Comuns
Guia prático para receiver:
- Use ponteiro se o método modifica o receiver
- Use ponteiro se a struct for grande (evita cópia dispendiosa)
- Use valor para tipos pequenos e imutáveis (int, string, etc.)
- Seja consistente: se um método usa ponteiro, todos os métodos do tipo devem usar
Armadilhas comuns:
- Métodos que não acessam o receiver: isso geralmente indica que deveria ser uma função, não um método.
// Ruim: método que ignora o receiver
func (r Retangulo) CalcularPI() float64 {
return 3.14159
}
// Melhor: função independente
func CalcularPI() float64 {
return 3.14159
}
-
Métodos muito longos: cada método deve ter uma única responsabilidade clara.
-
Nomeação inconsistente: use nomes descritivos e consistentes (ex:
GetNome,SetNomeem vez dePegarNome,Nome). -
Esquecer nil check: sempre proteja métodos que podem receber ponteiros nil.
Métodos são uma das características mais elegantes de Go, permitindo associar comportamento a dados de forma clara e eficiente. Quando usados corretamente, tornam o código mais expressivo, seguro e fácil de manter.
Referências
- Documentação Oficial: Methods — Tour interativo de Go explicando métodos com exemplos práticos
- Effective Go: Methods — Guia oficial de boas práticas sobre métodos em Go
- Go by Example: Methods — Exemplos concisos e diretos de métodos com receiver por valor e ponteiro
- A Theory of Go Methods — Artigo aprofundado de Ardan Labs sobre teoria e design de métodos
- Methods in Go: A Practical Guide — Guia prático com exemplos do mundo real e armadilhas comuns
- Go Wiki: Method Sets — Explicação detalhada sobre conjuntos de métodos e regras de seleção