Menu

Métodos em Golang: receiver de valor ou de ponteiro, explicado

Um método é uma função com um receiver. Veja como declarar métodos, quando usar receiver de valor ou de ponteiro, conjuntos de métodos e interfaces, e métodos em tipos que não são structs.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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 quandoUse receiver de valor quando
O método modifica o receiverO 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 parecidoVocê 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 é T ou *T que 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR