Qual a diferença entre valor e referência (ponteiro)

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!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta