O que é Method em Go?
Um method (método) em Go é uma função associada a um tipo específico por meio de um parâmetro especial chamado receiver. Diferente de linguagens orientadas a objetos tradicionais como Java ou C#, onde métodos vivem dentro de classes, em Go os métodos são definidos fora da declaração do tipo — mas vinculados a ele pelo receiver.
Métodos são fundamentais em Go porque são o mecanismo que permite que tipos satisfaçam interfaces. Quando você declara um método em um tipo, está dando comportamento a esse tipo, e é esse comportamento que determina quais interfaces ele implementa implicitamente.
A sintaxe básica de um método em Go segue este padrão:
func (receiver TipoReceiver) NomeMetodo(parametros) retorno {
// corpo do método
}
O receiver aparece entre a palavra-chave func e o nome do método. Esse detalhe sintático é o que diferencia um método de uma func comum.
Declarando métodos em tipos
Métodos em structs
A forma mais comum de usar métodos em Go é associá-los a structs:
type Circulo struct {
Raio float64
}
func (c Circulo) Area() float64 {
return math.Pi * c.Raio * c.Raio
}
func (c Circulo) Perimetro() float64 {
return 2 * math.Pi * c.Raio
}
func main() {
circ := Circulo{Raio: 5}
fmt.Printf("Área: %.2f\n", circ.Area()) // Área: 78.54
fmt.Printf("Perímetro: %.2f\n", circ.Perimetro()) // Perímetro: 31.42
}
Métodos em tipos não-struct
Você pode declarar métodos em qualquer tipo definido no mesmo package, não apenas structs. Isso é muito útil para tipos baseados em tipos primitivos:
type Celsius float64
type Fahrenheit float64
func (c Celsius) ParaFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func (f Fahrenheit) ParaCelsius() Celsius {
return Celsius((f - 32) * 5 / 9)
}
type StringList []string
func (sl StringList) Contem(alvo string) bool {
for _, s := range sl {
if s == alvo {
return true
}
}
return false
}
func (sl StringList) Filtrar(fn func(string) bool) StringList {
var resultado StringList
for _, s := range sl {
if fn(s) {
resultado = append(resultado, s)
}
}
return resultado
}
A restrição é que o tipo deve ser definido no mesmo pacote onde o método é declarado. Você não pode adicionar métodos a tipos de outros pacotes nem a tipos built-in diretamente — precisa criar um tipo derivado primeiro.
Value receiver vs pointer receiver
A escolha entre receiver de valor e receiver de ponteiro é uma das decisões mais importantes ao declarar métodos em Go:
type Conta struct {
Titular string
Saldo float64
}
// Value receiver — recebe uma CÓPIA da struct
func (c Conta) MostrarSaldo() string {
return fmt.Sprintf("Saldo de %s: R$ %.2f", c.Titular, c.Saldo)
}
// Pointer receiver — recebe um PONTEIRO, pode modificar a struct original
func (c *Conta) Depositar(valor float64) {
c.Saldo += valor
}
func (c *Conta) Sacar(valor float64) error {
if valor > c.Saldo {
return fmt.Errorf("saldo insuficiente: R$ %.2f", c.Saldo)
}
c.Saldo -= valor
return nil
}
Receiver de valor (T) | Receiver de ponteiro (*T) |
|---|---|
| Recebe cópia do valor | Recebe ponteiro para o valor original |
| Não pode modificar o original | Pode modificar o valor original |
| Seguro para uso concorrente (cópia isolada) | Requer mutex para acesso concorrente |
| Ideal para tipos pequenos e imutáveis | Ideal para structs grandes ou que precisam mutar |
A regra prática: se qualquer método precisa de pointer receiver, use pointer receiver em todos os métodos do tipo para manter consistência no method set.
Method sets e interfaces
O method set de um tipo determina quais interfaces esse tipo satisfaz. Essa é uma regra crucial em Go:
- O method set de
Tinclui métodos com receiverT - O method set de
*Tinclui métodos com receiverTe*T
type Formatador interface {
Formatar() string
}
type Modificador interface {
Modificar(string)
}
type Documento struct {
Conteudo string
}
func (d Documento) Formatar() string {
return strings.ToUpper(d.Conteudo)
}
func (d *Documento) Modificar(novo string) {
d.Conteudo = novo
}
func main() {
doc := Documento{Conteudo: "hello"}
var f Formatador = doc // OK: Documento tem Formatar()
var m Modificador = &doc // OK: *Documento tem Modificar()
// var m2 Modificador = doc // ERRO: Documento não tem Modificar()
_ = f
_ = m
}
Essa assimetria existe porque Go não pode garantir que sempre terá acesso ao endereço de um valor. Se você armazena um valor em uma interface, o Go pode não conseguir obter o ponteiro original — por isso T não satisfaz interfaces que exigem métodos com receiver *T.
Method expressions e method values
Go oferece duas formas avançadas de referenciar métodos como valores de primeira classe:
Method expression
Transforma o método em uma func regular onde o primeiro argumento é o receiver:
type Calculadora struct {
Fator float64
}
func (c Calculadora) Multiplicar(x float64) float64 {
return x * c.Fator
}
func main() {
// Method expression: tipo.Método → func(receiver, args) retorno
multiplicar := Calculadora.Multiplicar
calc := Calculadora{Fator: 3}
resultado := multiplicar(calc, 10) // 30
fmt.Println(resultado)
}
Method value
Vincula o método a uma instância específica, criando um closure:
func main() {
calc := Calculadora{Fator: 5}
// Method value: instância.Método → func(args) retorno
mult := calc.Multiplicar
fmt.Println(mult(10)) // 50
fmt.Println(mult(20)) // 100
}
Method values são úteis para passar métodos como callbacks, por exemplo ao usar goroutines ou ao satisfazer interfaces funcionais.
Padrões práticos com métodos
Builder pattern com method chaining
type QueryBuilder struct {
tabela string
condicoes []string
limite int
}
func NovaQuery(tabela string) *QueryBuilder {
return &QueryBuilder{tabela: tabela, limite: -1}
}
func (q *QueryBuilder) Where(condicao string) *QueryBuilder {
q.condicoes = append(q.condicoes, condicao)
return q
}
func (q *QueryBuilder) Limit(n int) *QueryBuilder {
q.limite = n
return q
}
func (q *QueryBuilder) Build() string {
sql := "SELECT * FROM " + q.tabela
if len(q.condicoes) > 0 {
sql += " WHERE " + strings.Join(q.condicoes, " AND ")
}
if q.limite > 0 {
sql += fmt.Sprintf(" LIMIT %d", q.limite)
}
return sql
}
func main() {
query := NovaQuery("usuarios").
Where("ativo = true").
Where("idade > 18").
Limit(10).
Build()
fmt.Println(query)
// SELECT * FROM usuarios WHERE ativo = true AND idade > 18 LIMIT 10
}
Implementando interfaces da biblioteca padrão
Métodos permitem que seus tipos implementem interfaces como fmt.Stringer, sort.Interface e encoding.json.Marshaler:
type Produto struct {
Nome string
Preco float64
}
// Implementa fmt.Stringer
func (p Produto) String() string {
return fmt.Sprintf("%s (R$ %.2f)", p.Nome, p.Preco)
}
// Implementa sort.Interface para []Produto
type PorPreco []Produto
func (pp PorPreco) Len() int { return len(pp) }
func (pp PorPreco) Less(i, j int) bool { return pp[i].Preco < pp[j].Preco }
func (pp PorPreco) Swap(i, j int) { pp[i], pp[j] = pp[j], pp[i] }
Esse padrão é amplamente utilizado na biblioteca padrão de Go e em projetos como os descritos em ferramentas essenciais de Go.
Métodos e concorrência
Ao usar métodos com goroutines e channels, é importante considerar se o receiver é seguro para acesso concorrente:
type ContadorSeguro struct {
mu sync.Mutex
valor int
}
func (c *ContadorSeguro) Incrementar() {
c.mu.Lock()
defer c.mu.Unlock()
c.valor++
}
func (c *ContadorSeguro) Valor() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.valor
}
Para aprofundar em concorrência, veja o tutorial de padrões de concorrência em Go e a entrada sobre select.
Boas práticas
- Consistência de receiver: se um método usa pointer receiver, todos os métodos do tipo devem usar pointer receiver
- Nome curto para receiver: use uma ou duas letras derivadas do tipo (
cparaConta,qbparaQueryBuilder) - Não use nomes genéricos: evite
thisouself— não é idiomático em Go - Métodos devem pertencer ao tipo correto: coloque o método no tipo que “possui” o dado
- Prefira funções para operações sem estado: se o método não usa o receiver, considere usar uma função regular
Para mais sobre funções e como elas se relacionam com métodos, consulte a entrada sobre func e o guia de Go para iniciantes.
Perguntas frequentes (FAQ)
Qual a diferença entre method e function em Go?
Uma function em Go é independente e não está associada a nenhum tipo. Um method é uma function com um receiver — um parâmetro especial que vincula a função a um tipo. Enquanto funções são chamadas como MinhaFunc(), métodos são chamados como instancia.MeuMetodo(). Métodos permitem que tipos satisfaçam interfaces e são essenciais para o sistema de tipos de Go.
Posso declarar métodos em tipos de outros pacotes?
Não. Em Go, você só pode declarar métodos em tipos definidos no mesmo package. Para adicionar métodos a um tipo externo, crie um tipo derivado: type MeuTipo TipoExterno. Isso é intencional no design da linguagem para manter a clareza sobre quais métodos pertencem a cada tipo.
Quando usar value receiver e quando usar pointer receiver?
Use pointer receiver quando o método precisa modificar o receiver, quando a struct é grande (evita cópia), ou quando outros métodos do tipo já usam pointer receiver. Use value receiver para tipos pequenos e imutáveis como time.Time. Na dúvida, prefira pointer receiver — é a escolha mais segura e idiomática.
O que é method set e por que ele importa?
O method set de um tipo determina quais interfaces ele satisfaz. O method set de T inclui apenas métodos com receiver T, enquanto o method set de *T inclui métodos com receiver T e *T. Isso significa que um valor T pode não satisfazer uma interface que exige métodos com pointer receiver, mas *T sempre satisfaz ambos.