Skip to content

Métodos do Go

A diferença entre métodos e funções é que métodos têm um receptor, enquanto funções não, e apenas tipos personalizados podem ter métodos. Vejamos um exemplo.

go
type IntSlice []int

func (i IntSlice) Get(index int) int {
  return i[index]
}
func (i IntSlice) Set(index, val int) {
  i[index] = val
}

func (i IntSlice) Len() int {
  return len(i)
}

Primeiro foi declarado um tipo IntSlice, seu tipo subjacente é []int, depois foram declarados três métodos Get, Set e Len, a aparência do método não é muito diferente da função, apenas tem um pequeno trecho a mais (i IntSlice). i é o receptor, IntSlice é o tipo do receptor, o receptor é similar ao this ou self em outras linguagens, só que em Go precisa ser especificado explicitamente.

go
func main() {
   var intSlice IntSlice
   intSlice = []int{1, 2, 3, 4, 5}
   fmt.Println(intSlice.Get(0))
   intSlice.Set(0, 2)
   fmt.Println(intSlice)
   fmt.Println(intSlice.Len())
}

O uso do método é similar a chamar um método membro de uma classe, primeiro declara, depois inicializa, depois chama.

Receptor de Valor

O receptor também tem dois tipos, receptor de valor e receptor de ponteiro, primeiro veja um exemplo

go
type MyInt int

func (i MyInt) Set(val int) {
   i = MyInt(val) // modificou, mas não vai causar nenhum impacto
}

func main() {
   myInt := MyInt(1)
   myInt.Set(2)
   fmt.Println(myInt)
}

Após executar o código acima, vai descobrir que o valor de myInt ainda é 1, não foi modificado para 2. Quando o método é chamado, o valor do receptor é passado para o método, o receptor no exemplo acima é um receptor de valor, pode ser considerado simplesmente como um parâmetro formal, e modificar o valor de um parâmetro formal não causa nenhum impacto no valor fora do método, então e se chamar através de ponteiro?

go
func main() {
  myInt := MyInt(1)
  (&myInt).Set(2)
  fmt.Println(myInt)
}

Infelizmente, esse código ainda não consegue modificar o valor interno, para conseguir corresponder ao tipo do receptor, Go vai fazer a desreferenciação, interpretando como (*(&myInt)).Set(2).

Receptor de Ponteiro

Com uma pequena modificação, já é possível modificar o valor de myInt normalmente.

go
type MyInt int

func (i *MyInt) Set(val int) {
   *i = MyInt(val)
}

func main() {
   myInt := MyInt(1)
   myInt.Set(2)
   fmt.Println(myInt)
}

Agora o receptor é um receptor de ponteiro, embora myInt seja um tipo de valor, ao chamar um método com receptor de ponteiro através de um tipo de valor, Go vai interpretar como (&myint).Set(2). Então quando o receptor do método é um ponteiro, independentemente de o chamador ser ou não um ponteiro, é possível modificar o valor interno.

No processo de passagem de parâmetros de função, é passagem por valor, se for passado um inteiro, então copia esse inteiro, se for um slice, então copia esse slice, mas se for um ponteiro, só precisa copiar esse ponteiro, obviamente passar um ponteiro consome menos recursos do que passar um slice, o receptor não é exceção, receptor de valor e receptor de ponteiro seguem o mesmo princípio. Na maioria dos casos, recomenda-se usar receptor de ponteiro, mas os dois não devem ser usados de forma mista, ou usa ambos, ou não usa nenhum, veja o exemplo abaixo.

TIP

Precisa entender primeiro interfaces

go
type Animal interface {
   Run()
}

type Dog struct {
}

func (d *Dog) Run() {
   fmt.Println("Run")
}

func main() {
   var an Animal
   an = Dog{}
   // an = &Dog{} forma correta
   an.Run()
}

Este código não vai compilar, o compilador vai mostrar o seguinte erro

cannot use Dog{} (value of type Dog) as type Animal in assignment:
  Dog does not implement Animal (Run method has pointer receiver)

Traduzindo, não é possível usar Dog{} para inicializar uma variável do tipo Animal, porque Dog não implementa Animal, há duas soluções, uma é mudar o receptor de ponteiro para receptor de valor, a outra é mudar Dog{} para &Dog{}, a seguir vamos explicar cada uma.

go
type Dog struct {
}

func (d Dog) Run() { // mudou para receptor de valor
   fmt.Println("Run")
}

func main() { // pode executar normalmente
   var an Animal
   an = Dog{}
   // an = &Dog{} também funciona
   an.Run()
}

No código original, o receptor do método Run é *Dog, naturalmente quem implementa a interface Animal é o ponteiro Dog, não a struct Dog, são dois tipos diferentes, então o compilador vai achar que Dog{} não é uma implementação de Animal, portanto não pode ser atribuído à variável an, então a segunda solução é atribuir um ponteiro de Dog à variável an. No entanto, ao usar receptor de valor, o ponteiro Dog ainda pode ser atribuído normalmente a animal, isso porque Go vai desreferenciar ponteiros quando apropriado, porque através do ponteiro pode-se encontrar a struct Dog, mas o contrário não funciona, não é possível encontrar o ponteiro Dog através da struct Dog. Se simplesmente misturar receptor de valor e receptor de ponteiro em uma struct não há problema, mas usado junto com interfaces vai causar erros, melhor então sempre usar receptor de valor ou sempre usar receptor de ponteiro, formando uma boa convenção, também pode reduzir a carga de manutenção posterior.

Há outro caso, quando o receptor de valor é endereçável, Go vai inserir automaticamente o operador de ponteiro para fazer a chamada, por exemplo slice é endereçável, ainda é possível modificar seus valores internos através de receptor de valor. Por exemplo, o código abaixo

go
type Slice []int

func (s Slice) Set(i int, v int) {
  s[i] = v
}

func main() {
  s := make(Slice, 1)
  s.Set(0, 1)
  fmt.Println(s)
}

Saída

[1]

Mas isso vai causar outro problema, se adicionar elementos a ele, a situação é diferente. Veja o exemplo abaixo

type Slice []int

func (s Slice) Set(i int, v int) {
  s[i] = v
}

func (s Slice) Append(a int) {
  s = append(s, a)
}

func main() {
  s := make(Slice, 1, 2)
  s.Set(0, 1)
  s.Append(2)
  fmt.Println(s)
}
[1]

Sua saída ainda é a mesma de antes, a função append tem valor de retorno, após adicionar elementos ao slice deve-se sobrescrever o slice original, especialmente após expansão, modificar o receptor de valor no método não causa nenhum impacto, isso também causa o resultado no exemplo, mudando para receptor de ponteiro fica normal.

go
type Slice []int

func (s *Slice) Set(i int, v int) {
  (*s)[i] = v
}

func (s *Slice) Append(a int) {
  *s = append(*s, a)
}

func main() {
  s := make(Slice, 1, 2)
  s.Set(0, 1)
  s.Append(2)
  fmt.Println(s)
}

Saída

[1 2]

Golang por www.golangdev.cn edit