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.
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.
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
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?
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.
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
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.
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
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.
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]