Declarando um método
Um método é uma função com um receiver: um parâmetro extra escrito antes do nome do método. O receiver liga o método a um tipo.
O nome do receiver costuma ter uma ou duas letras tiradas do tipo (c para Circle). Go não usa this nem self, e os guias de estilo desencorajam esses nomes.
Como Circle tem um método String() string, ele satisfaz fmt.Stringer, e o fmt.Println imprime o resultado em vez de {2}.
Receivers de valor e receivers de ponteiro
Um receiver de valor (c Circle) recebe uma cópia. Um receiver ponteiro (c *Circle) recebe o endereço, então as mudanças nos campos permanecem.
IncByValue compila e não faz nada visível, um bug clássico. Se um método deve modificar o seu receiver, ele precisa de receiver ponteiro.
As chamadas também mostram as duas conveniências que o Go oferece. c.Inc() em uma variável endereçável é reescrito como (&c).Inc(), e p.Value() em um ponteiro é reescrito como (*p).Value(). Você raramente escreve & ou * só para chamar um método.
O & automático precisa de algo endereçável. Um elemento de map ou um literal composto não é:
m := map[string]Counter{"a": {}}
m["a"].Inc() // compile error: cannot call pointer method Inc on Counter
Counter{}.Inc() // same error: a composite literal is not addressable
Guarde ponteiros no map (map[string]*Counter) ou copie o valor para fora, altere e grave de volta.
Qual receiver escolher
| Use receiver ponteiro quando | Use receiver de valor quando |
|---|---|
| O método modifica o receiver | O tipo é pequeno e nunca é modificado pelos seus métodos (time.Time, um ponto) |
| A struct é grande (copiar custa) | O tipo é um map, func ou chan (já é uma referência) |
O tipo contém um sync.Mutex ou algo parecido | Você quer que o valor se comporte como um primitivo, seguro para copiar |
| Outros métodos do tipo já usam ponteiros |
O FAQ do Go e a biblioteca padrão seguem uma regra prática: seja consistente. Se algum método precisa de receiver ponteiro, dê receiver ponteiro a todos os métodos, para que o conjunto de métodos seja previsível. O go vet aponta uma struct com um sync.Mutex que é copiada por meio de um receiver de valor (passes lock by value).
Conjuntos de métodos e interfaces
A diferença importa mais nas interfaces. O conjunto de métodos de T contém os métodos com receiver de valor. O conjunto de métodos de *T contém os métodos com receiver de valor e os com receiver ponteiro.
Escrever Square{Side: 3} sem o & naquele slice não compila:
cannot use Square{…} (value of struct type Square) as Shape value in array or slice literal: Square does not implement Shape (method Area has pointer receiver)
O motivo: uma interface pode guardar uma cópia de um Square que não é endereçável, então o Go não consegue pegar o endereço dela para chamar um método de ponteiro. Veja interfaces para o resto dessa história.
Métodos em tipos que não são structs
Qualquer tipo nomeado declarado no seu pacote pode ter métodos, não só structs.
Os limites: o tipo precisa ser definido no mesmo pacote que o método, e o seu tipo subjacente não pode ser um ponteiro ou uma interface. Você não pode acrescentar métodos a int, string ou time.Time a partir do seu pacote. Envolver em um novo tipo nomeado é o jeito Go. String() em um tipo inteiro também é como enums ganham uma saída legível; veja enums com iota.
Method values e method expressions
Um método ligado a um receiver específico é um valor de função:
Um method value com receiver de valor copia o receiver no momento em que é criado, e é por isso que f ainda diz Ana. Com receiver ponteiro, ele guardaria o ponteiro e veria as mudanças posteriores. Method values são práticos para callbacks: http.HandleFunc("/", srv.handleIndex).
Erros comuns
- Receiver de valor em um método que altera o estado. Compila e altera uma cópia em silêncio.
- Misturar tipos de receiver. Deixa pouco claro se é
Tou*Tque satisfaz uma interface. Escolha um por tipo. - Chamar um método de ponteiro em um ponteiro nil. É permitido, e o método executa com um receiver nil. Ele só causa panic quando acessa um campo. Alguns tipos (como uma lista ligada segura para nil) usam isso de propósito; a maioria não espera por isso.
Perguntas frequentes
Qual a diferença entre método e função em Go?
Um método tem um receiver, escrito entre func e o nome: func (c Circle) Area() float64. Você o chama sobre um valor (c.Area()), ele pertence ao conjunto de métodos do tipo e é por meio dele que um tipo satisfaz interfaces. Fora isso, ele se comporta como uma função cujo primeiro argumento é o receiver.
Quando usar um receiver ponteiro em Go?
Use um receiver ponteiro quando o método precisa modificar o receiver, quando a struct é grande o bastante para que copiá-la a cada chamada seja desperdício, ou quando o tipo contém algo que não pode ser copiado, como um sync.Mutex. Se algum método de um tipo precisa de receiver ponteiro, o conselho de sempre é fazer todos com receiver ponteiro, por consistência.
Por que meu tipo não implementa a interface quando o método tem receiver ponteiro?
Métodos com receiver ponteiro pertencem ao conjunto de métodos de *T, não de T. Então var s Shape = Square{} falha com Square does not implement Shape (method Area has pointer receiver). Atribua um ponteiro: var s Shape = &Square{}.
Dá para definir métodos em tipos embutidos em Go?
Não diretamente. Métodos só podem ser declarados em tipos nomeados definidos no mesmo pacote. Defina antes o seu próprio tipo, type Celsius float64, e depois acrescente métodos a ele. Você não pode acrescentar métodos a int, string ou a um tipo de outro pacote.