DEV Community

renanbastos93
renanbastos93

Posted on

Qual a diferença de usar um receiver com ou sem ponteiro em Go?

Recentemente, tive a incrível oportunidade de participar de um dos maiores eventos de Golang da América Latina, a Gophercon BR. Gostaria de expressar minha gratidão aos organizadores e a todos que estiveram presentes no evento, pois proporcionaram um ambiente incrível para networking e aprendizado.

Durante o evento, tive um encontro interessante com um rapaz chamado João Salvador. Ele apresentou um desafio intrigante, exibindo um código que imediatamente despertou minha curiosidade e fez meus neurônios entrarem em ação para decifrá-lo. A experiência de resolver esse desafio foi estimulante e educativa, e, após concluí-lo, senti a necessidade de compartilhar esse conhecimento com a comunidade.

Neste post, estou entusiasmado em apresentar o desafio proposto por João Salvador e explicar os detalhes por trás dele. Acredito que a compreensão deste desafio trará uma nova perspectiva sobre o uso de receivers com ou sem ponteiro em Go, proporcionando insights valiosos que podem expandir nossos horizontes e ajudar muitas pessoas a aprofundar seu entendimento da linguagem.


Desafio que me foi Apresentado no Evento

O que você espera que o console imprima? Pense bem antes de continuar lendo, isso vai enriquecer ainda mais sua compreensão no tópico em questão

Image description

Comente a sua resposta!


Agora iremos juntos entender e desvendar a solução

Vamos começar nossa jornada entendendo que temos uma estrutura chamada "Person". Cada instância dessa estrutura possui um atributo chamado "Name", que armazena o nome de uma pessoa. Além disso, esta estrutura é enriquecida com um método chamado "SayHi", que é acionado por meio de um conceito conhecido como "receiver". A função principal deste método é simples, ele imprime na tela uma saudação, "Hi", seguida pelo nome da pessoa.

// Declarada a estrutura Person
type Person struct {
    Name string
}

// SayHi vai printar uma saudação Hi seguida com o nome da pessoa
func (e Person) SayHi() {
    fmt.Printf("Hi %s!\n", e.Name)
}
Enter fullscreen mode Exit fullscreen mode

Tá! Mas o que é receiver?

É um parâmetro especial em um método de um tipo definido pelo usuário (struct) que permite que esse método seja chamado em instâncias desse tipo. O receiver é semelhante ao conceito de um objeto em orientação a objetos, mas é específico para métodos em Go.

Observamos que o método SayHi utiliza um receiver de valor, o que significa que, nesse contexto, o método atua em uma cópia do valor original do tipo. Isso implica que qualquer alteração efetuada dentro do método não terá efeito sobre a instância original desse tipo.

Agora que entendemos isso vamos ver como funciona um receiver de ponteiro. Ou seja, o método opera diretamente na instância original do tipo, pois recebe um ponteiro para ela. Isso permite que o método modifique o valor da instância original.

func (p *Person) UpdateName(newName string) {
    p.Name = newName
}
Enter fullscreen mode Exit fullscreen mode

Agora que entendemos ambos os casos comentado a cima, vamos criar nossa func main() para visualizar e entender os outputs gerados.

func main() {
    var p = Person{Name: "Renan"}
    defer p.SayHi()              // Output: Hi Renan!
    defer func() { p.SayHi() }() // Output: Hi Renan Bastos!

    p.UpdateName("Renan Bastos")
}
Enter fullscreen mode Exit fullscreen mode

Parece bruxaria mas não é, os dois defer imprimiram valores diferentes porque o defer empilha uma cópia da estrutura no primeiro e uma função anônima no segundo. Portanto, quando o defer é executado, ele já capturou a alteração no campo Name. O defer funciona como uma pilha, executando o último defer chamado primeiro e desempilhando conforme cada um é executado.

// Output final
// Output: Hi Renan Bastos!
// Output: Hi Renan!
Enter fullscreen mode Exit fullscreen mode

Conclusão

Neste artigo, exploramos o conceito de receiver em Go e a utilidade do defer. Uma questão frequente é quando devemos optar por usar um receiver de ponteiro. A escolha depende das necessidades específicas de seu programa e do comportamento desejado, e não existe uma resposta única. Portanto, a melhor abordagem é avaliar cuidadosamente o contexto de seu código e escolher entre receiver de valor ou de ponteiro com base nas exigências do projeto.

Top comments (0)