Hoje vamos abordar um tema muito interessante e que é comum a quase todas as linguagens, valor vs referência (também conhecida como ponteiro).
Primeiramente, não podemos nos esquecer que, cada variável que criamos, independente do seu tipo, assim como arrays, slices e maps, são espaços alocados em memória.
Outro ponto importante que temos que ter em mente é que, os parâmetros de uma função também são variáveis.
Para ajudar na explicação, vamos criar uma função com 2 parâmetros, onde o primeiro espera um valor e o segundo um ponteiro.
func ValPoint(valor string, ponteiro *string) {
fmt.Println(valor)
fmt.Println(ponteiro)
}
Como podemos ver, sempre que um parâmetro esperar um ponteiro, devemos colocar um * antes do tipo de dados. Agora, na função main vamos chamar essa função que acabamos de criar.
package main
func main() {
v := "valor"
p := "ponteiro"
ValPoint(v, &p)
}
Em Go, sempre que queremos passar o ponteiro de uma variável que foi criada por valor, precisamos adicionar um & antes do nome da variável.
Se você fizer um go run main.go provavelmente terá um resultado semelhante a esse:
valor
0xc00009e210
Isso acontece por que, quando passamos uma variável por valor, uma cópia dessa variável é criada e passada para dentro da função. Já quando passamos uma variável por ponteiro, só é passado a referência da memória onde o valor está.
Só por curiosidade, experimente mudar o segundo print da função para fmt.Println(*ponteiro).
Ok, mas se o ponteiro não gera uma cópia do dado, então para economizar memória é melhor passar tudo por ponteiro, certo?
Claro… que não..
Vamos dar uma olhada nesse outro exemplo.
package main
import (
"fmt"
)
type Pessoa struct {
Nome string
Idade int8
}
func ValPoint(valor Pessoa, ponteiro *Pessoa) {
fmt.Printf("func: %s - %d\n", valor.Nome, valor.Idade)
fmt.Printf("func: %s - %d\n", ponteiro.Nome, ponteiro.Idade)
valor.Idade += 10
ponteiro.Idade += 10
}
func main() {
v := Pessoa{"Dani", 36}
p := Pessoa{"Tiago", 31}
ValPoint(v, &p)
fmt.Printf("out: %s - %d\n", v.Nome, v.Idade)
fmt.Printf("out: %s - %d\n", p.Nome, p.Idade)
}
Ao executar esse programa, teremos o seguinte resultado.
func: Dani - 36
func: Tiago - 31
out: Dani - 36
out: Tiago - 41
Como passamos o p por ponteiro, qualquer mudança que houver na struct dentro da função, irá se refletir fora, o que na maioria das vezes não é algo que queremos.
É claro que temos casos onde isso é muito útil, como por exemplo, funções que buscam dados em bancos de dados e já atribuem o resultado à uma variável que criamos, decode de arquivos JSON, YAML e etc…
A utilização de ponteiros deve ser feita com muita cautela. Dois bons exemplos de utilização é para conexão com banco de dados, já que assim conseguimos reutilizar uma conexão que já está aberta, e em casos onde temos que garantir que um determinado objeto é igual em qualquer parte da aplicação, como por exemplo uma request.
Deixem suas dúvidas nos comentários.
Até a próxima!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre GO.

