URL: https://www.progressiverobot.com/understanding-pointers-in-go-pt/

Introdução

Quando você criar software em Go, você estará escrevendo funções e métodos. Você envia dados para essas funções como _argumentos_. Às vezes, a função precisa de uma cópia local dos dados e você quer que o original permaneça inalterado. Por exemplo, se você for um banco e tiver uma função que mostra ao usuário as alterações de seu saldo – dependendo do tipo de conta (poupança/investimentos) que o usuário escolha. Neste caso, você não vai querer alterar o saldo real do cliente antes que ele escolha o tipo de conta/plano de investimentos, mas apenas usar a informação em cálculos. Esse parâmetro é chamado de _passagem por valor_ porque você está enviando o valor da variável para a função, mas não a variável em si.

Outras vezes, você pode querer que a função seja capaz de alterar os dados na variável original. Por exemplo, quando o cliente bancário faz um depósito em sua conta, você quer que a função de depósito possa acessar o saldo real, não uma cópia. Nesse caso, você não precisa enviar os dados reais da função; precisa apenas dizer à função onde os dados estão localizados na memória. Um tipo de dados chamado _ponteiro_ retém o endereço da memória dos dados, mas não os dados em si. O endereço da memória diz à função onde encontrar os dados, mas não o valor dos dados. Você pode enviar o ponteiro para a função em vez dos dados e a função poderá, então, alterar a variável original em seu lugar original. Isso é chamado de _passagem por referência_ porque o valor da variável não é enviado para a função, apenas sua localização.

Neste artigo, você criará e usará ponteiros para compartilhar o acesso ao espaço de memória de uma variável.

Definindo e usando ponteiros

ponteiros illustration for: Definindo e usando ponteiros

Quando você usa um ponteiro para uma variável, há alguns elementos de sintaxe diferentes que você precisa entender. O primeiro é o uso do "E comercial" (&). Se colocar um e comercial na frente de um nome de variável, você está declarando que quer obter o _endereço_ ou um ponteiro para aquela variável. O segundo elemento de sintaxe é o uso do asterisco (*) ou do operador de _desreferenciação_. Quando declara uma variável de ponteiro, você segue o nome da variável para a qual o ponteiro aponta, prefixado com um *, desta forma:

				
					
var myPointer *int32 = &someint

				
			

Isso cria o myPointer como um ponteiro para uma variável int32 e inicializa o ponteiro com o endereço de someint. Na verdade, o ponteiro não contém um int32, mas apenas o endereço de um.

Vejamos um ponteiro para uma string. O código a seguir declara tanto um valor de uma string quanto um ponteiro para uma string:

				
					
[label main.go]

package main



import "fmt"



func main() {

	var creature string = "shark"

	var pointer *string = &creature



	fmt.Println("creature =", creature)

	fmt.Println("pointer =", pointer)

}



				
			

Execute o programa com o seguinte comando:

				
					
go run main.go

				
			

Quando você executar o programa, ele imprimirá o valor da variável, além do endereço onde a variável está armazenada (o endereço do ponteiro). O endereço de memória é um número hexadecimal e não se destina à leitura por humanos. Na prática, é provável que você nunca veja um endereço de memória como resultado. Estamos mostrando a você somente a título de ilustração. Como cada programa é criado em seu próprio espaço de memória quando é executado, o valor do ponteiro será diferente cada vez que você o executar e será diferente do resultado mostrado aqui:

				
					
[secondary_label Output]

creature = shark

pointer = 0xc0000721e0

				
			

A primeira variável que definimos chamamos de creature e a definimos como sendo igual a uma string com o valor de shark. Depois, criamos outra variável chamada pointer. Desta vez, definimos o valor da variável pointer para o endereço da variável creature. Armazenamos o endereço de um valor em uma variável, usando o símbolo do e comercial (&). Isso significa que a variável pointer está armazenando o endereço da variável creature, não o valor real.

É por isso que, quando imprimimos o valor de pointer, recebemos o valor de 0xc0000721e0, que é o endereço onde a variável creature está armazenada na memória do computador no momento.

Se quiser imprimir o valor da variável para a qual a variável pointer está apontando, será necessário _desreferenciar_ essa variável. O código a seguir usa o operador * para desreferenciar a variável pointer e recuperar seu valor:

				
					
[label main.go]



package main



import "fmt"



func main() {

	var creature string = "shark"

	var pointer *string = &creature



	fmt.Println("creature =", creature)

	fmt.Println("pointer =", pointer)



	<^>fmt.Println("*pointer =", *pointer)<^>

}

				
			

Se executar esse código, você verá o seguinte resultado:

				
					
[secondary_label Output]

creature = shark

pointer = 0xc000010200

*pointer = shark

				
			

A última linha que adicionamos agora desreferencia a variável pointer e imprime o valor armazenado naquele endereço.

Se quiser modificar o valor armazenado na localização da variável pointer, você também poderá usar o operador de desreferenciamento:

				
					
[label main.go]

package main



import "fmt"



func main() {

	var creature string = "shark"

	var pointer *string = &creature



	fmt.Println("creature =", creature)

	fmt.Println("pointer =", pointer)



	fmt.Println("*pointer =", *pointer)



	<^>*pointer = "jellyfish"<^>

	<^>fmt.Println("*pointer =", *pointer)<^>

}



				
			

Execute este código para ver o resultado:

				
					
[secondary_label Output]

creature = shark

pointer = 0xc000094040

*pointer = shark

*pointer = jellyfish

				
			

Definimos o valor ao qual a variável pointer se refere, usando o asterisco (*) na frente do nome variável e, em seguida, fornecendo um novo valor de jellyfish. Como pode ver, ao imprimirmos o valor desreferenciado, ele passou a ser definido como jellyfish.

Talvez você não tenha percebido, mas também alteramos o valor da variável creature. Isso acontece porque a variável pointer está, na verdade, apontando para o endereço da variável creature. Isso significa que, se alterarmos o valor para o qual a variável pointer aponta, também vamos alterar o valor da variável creature.

				
					
[label main.go]

package main



import "fmt"



func main() {

	var creature string = "shark"

	var pointer *string = &creature



	fmt.Println("creature =", creature)

	fmt.Println("pointer =", pointer)



	fmt.Println("*pointer =", *pointer)



	*pointer = "jellyfish"

	fmt.Println("*pointer =", *pointer)



	<^>fmt.Println("creature =", creature)<^>

}

				
			

O resultado obtido fica parecido com o seguinte:

				
					
[secondary_label Output]

creature = shark

pointer = 0xc000010200

*pointer = shark

*pointer = jellyfish

creature = jellyfish

				
			

Embora esse código ilustra como um ponteiro funciona, não se trata da maneira usual que você usaria os ponteiro em Go. É mais comum usar os ponteiros ao definirmos argumentos de funções e valores de retorno ou ao definirmos métodos em tipos personalizados. Vejamos como você usaria os ponteiros com funções para compartilhar o acesso a uma variável.

Novamente, tenha em mente que estamos imprimindo o valor de pointer para ilustrar que se trata de um ponteiro. Na prática, você não usaria o valor de um ponteiro, a não ser para fazer referência ao valor subjacente – para recuperar ou atualizar aquele valor.

Ponteiros receptores da função

Quando você escreve uma função, você pode definir os argumentos a serem passados por _valor_ ou por _referência_. Passar por _valor_ significa dizer que uma cópia desse valor é enviada para a função e quaisquer alterações feitas no argumento – dentro daquela _função_ – afetam somente a variável naquela função e não o local de onde ela foi passada. No entanto, se você passar por _referência_, ou seja, se passar um ponteiro para aquele argumento, você poderá alterar o valor de dentro da função e também alterar o valor da variável original que foi enviado. Você pode ler mais sobre como definir funções em nosso artigo sobre [Como definir e chamar funções em Go]().

Decidir entre o momento de enviar um ponteiro – ao invés de enviar um valor – é predominantemente uma questão de saber se você deseja ou não que o valor mude. Se não quiser que o valor mude, envie-o como um valor. Se quiser que a função para a qual você está enviando sua variável possa alterá-la, então você a enviaria como um ponteiro.

Para saber a diferença, primeiramente, vejamos uma função que está enviando um argumento por value [valor]:

				
					
[label main.go]

package main



import "fmt"



type Creature struct {

	Species string

}



func main() {

	var creature Creature = Creature{Species: "shark"}



	fmt.Printf("1) %+v\n", creature)

	changeCreature(creature)

	fmt.Printf("3) %+v\n", creature)

}



func changeCreature(creature Creature) {

	creature.Species = "jellyfish"

	fmt.Printf("2) %+v\n", creature)

}



				
			

O resultado obtido fica parecido com o seguinte:

				
					
[secondary_label Output]

1) {Species:shark}

2) {Species:jellyfish}

3) {Species:shark}

				
			

Primeiro, criamos um tipo personalizado chamado Creature. Ele tem um campo chamado Species, que é uma string. Na função main, criamos uma instância do nosso novo tipo chamado creature e definimos o campo Species como shark. Na sequência, imprimimos a variável para mostrar o valor atual armazenado dentro da variável creature.

Em seguida, chamamos changeCreature e enviamos uma cópia da variável creature.

Definimos a função changeCreature como aquela que adota um argumento chamado creature – sendo do tipo Creature que definimos anteriormente. Então, mudamos o valor do campo Species para jellyfish e o imprimimos. Note que, dentro da função changeCreature, o valor de Species agora é jellyfish e ela imprime 2) {Species:jellyfish}. Isso acontece porque temos permissão para alterar o valor dentro do escopo da nossa função.

No entanto, quando a última linha da função main imprime o valor de creature, o valor de Species ainda é shark. A razão pela qual o valor não mudou é porque passamos a variável por _valor_. Isso significa que uma cópia do valor foi criada na memória e enviada para a função changeCreature. Isso nos permite ter uma função que pode fazer alterações em quaisquer argumentos enviados – conforme necessário. Tais alterações, porém, não vão afetar nenhuma daquelas variáveis fora da função.

Em seguida, vamos alterar a função changeCreature para que receba um argumento por _referência_. Podemos fazer isso alterando o tipo de creature para um ponteiro, usando o operador asterisco (*). Em vez de enviar uma creature, agora estamos enviando um ponteiro para uma creature, ou uma *creature. No exemplo anterior, creature é um struct cujo valor de Species é shark. O *creature é um ponteiro, não um struct; assim, o seu valor é uma localização de memória e é isso o que enviamos para a função changeCreature().

				
					
[label main.go]

package main



import "fmt"



type Creature struct {

	Species string

}



func main() {

	var creature Creature = Creature{Species: "shark"}



	fmt.Printf("1) %+v\n", creature)

	changeCreature(<^>&<^>creature)

	fmt.Printf("3) %+v\n", creature)

}



func changeCreature(creature <^>*<^>Creature) {

	creature.Species = "jellyfish"

	fmt.Printf("2) %+v\n", creature)

}

				
			

Execute este código para ver o seguinte resultado:

				
					
[secondary_label Output]

1) {Species:shark}

2) &{Species:jellyfish}

3) {Species:jellyfish}

				
			

Agora, observe que, quando alteramos o valor de Species para jellyfish na função changeCreature, isso também altera o valor original definido na função main. Isso acontece porque passamos a variável creature por _referência_, o que permite o acesso ao valor original e que pode alterá-lo conforme necessário.

Portanto, se quiser que uma função seja capaz de alterar um valor, você precisará passá-la por referência. Para passar por referência, você envia o ponteiro para a variável e não a variável em si.

No entanto, algumas vezes você pode não ter um valor real definido para um ponteiro. Nesses casos, é possível ter um pânico no programa. Vejamos como isso acontece e como se planejar para tal possível problema.

Ponteiros nil

Todas as variáveis em Go têm um valor zero. Isso é verdade mesmo em relação a um ponteiro. Se você declarar um ponteiro para um tipo, mas não atribuir um valor, o valor zero será nil. nil é uma maneira de dizer que "nothing has been initialized" (nada foi inicializado) em relação à variável.

No programa a seguir, vamos definir um ponteiro para um tipo Creature, mas jamais iremos instanciar a instância real de uma Creature e lhe atribuir seu endereço para a variável do ponteiro creature. O valor será nil e não poderemos fazer referência a nenhum dos campos ou métodos que seriam definidos no tipo Creature:

				
					
[label main.go]

package main



import "fmt"



type Creature struct {

	Species string

}



func main() {

	var creature *Creature



	fmt.Printf("1) %+v\n", creature)

	changeCreature(creature)

	fmt.Printf("3) %+v\n", creature)

}



func changeCreature(creature *Creature) {

	creature.Species = "jellyfish"

	fmt.Printf("2) %+v\n", creature)

}

				
			

O resultado obtido fica parecido com o seguinte:

				
					
[secondary_label Output]

1) <nil>

panic: runtime error: invalid memory address or nil pointer dereference

[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x109ac86]



goroutine 1 [running]:

main.changeCreature(0x0)

        /Users/corylanou/projects/learn/src/github.com/gopherguides/learn/_training/the cloud provider/pointers/src/nil.go:18 +0x26

	main.main()

	        /Users/corylanou/projects/learn/src/github.com/gopherguides/learn/_training/the cloud provider/pointers/src/nil.go:13 +0x98

		exit status 2

				
			

Quando executamos o programa, ele imprime o valor da variável creature e o valor é <nil>. Depois, chamamos a função changeCreature e, quando essa função tentar definir o valor do campo Species, o programa entra em _pânico_. Isso acontece porque, na verdade, nenhuma instância da variável foi criada. Por isso, o programa não tem, na verdade, onde armazenar o valor e, portanto, ele entra em pânico.

Se você estiver recebendo um argumento como um ponteiro, na linguagem Go é comum que você verifique se tal argumento estava ou não com valor nil – antes de realizar qualquer operação nele, no intuito de evitar que o programa entre em pânico.

Esta é uma abordagem comum para a verificação quanto ao valor nil:

				
					
if someVariable == nil {

	// print an error or return from the method or fuction

}

				
			

Na verdade, você quer assegurar que não haja um ponteiro nil que tenha sido enviado para a sua função ou método. Se tiver um ponteiro com valor nil, é provável que você queira somente retornar ou mesmo retornar um erro para mostrar que um argumento inválido foi enviado para a função ou método. O código a seguir demonstra como verificar se há nil:

				
					
[label main.go]

package main



import "fmt"



type Creature struct {

	Species string

}



func main() {

	var creature *Creature



	fmt.Printf("1) %+v\n", creature)

	changeCreature(creature)

	fmt.Printf("3) %+v\n", creature)

}



func changeCreature(creature *Creature) {

	&lt;^&gt;if creature == nil {&lt;^&gt;

		&lt;^&gt;fmt.Println("creature is nil")&lt;^&gt;

		&lt;^&gt;return&lt;^&gt;

	&lt;^&gt;}&lt;^&gt;



	creature.Species = "jellyfish"

	fmt.Printf("2) %+v\n", creature)

}

				
			

Adicionamos uma verificação na função changeCreature para ver se o valor do argumento creature era nil. Se o valor for nil, nós imprimimos "creature is nil" e o return fora da função. Caso contrário, devemos prosseguir e alterar o valor do campo Species. Se executarmos o programa agora, vamos receber o seguinte resultado:

				
					
[secondary_label Output]

1) &lt;nil&gt;

creature is nil

3) &lt;nil&gt;

				
			

Note que, embora ainda tenhamos um valor nil para a variável creature, não estamos mais em pânico, pois estamos inspecionando quanto a tal cenário.

Por fim, se criarmos uma instância do tipo Creature e a atribuirmos para a variável creature, o programa irá, então, alterar o valor como esperado:

				
					
[label main.go]

package main



import "fmt"



type Creature struct {

	Species string

}



func main() {

	var creature *Creature

	&lt;^&gt;creature = &amp;Creature{Species: "shark"}&lt;^&gt;



	fmt.Printf("1) %+v\n", creature)

	changeCreature(creature)

	fmt.Printf("3) %+v\n", creature)

}



func changeCreature(creature *Creature) {

	if creature == nil {

		fmt.Println("creature is nil")

		return

	}



	creature.Species = "jellyfish"

	fmt.Printf("2) %+v\n", creature)

}

				
			

Agora que temos uma instância do tipo Creature, o programa será executado e vamos obter o seguinte resultado esperado:

				
					
[secondary_label Output]

1) &amp;{Species:shark}

2) &amp;{Species:jellyfish}

3) &amp;{Species:jellyfish}

				
			

Quando você está trabalhando com ponteiros, existe a possibilidade do programa entrar em pânico. Para evitar o pânico, você deve verificar se um valor de ponteiro é nil antes de tentar acessar qualquer um dos campos ou métodos definidos nele.

Em seguida, vamos examinar de que modo o uso de ponteiros e valores afeta a definição de métodos em um tipo.

Ponteiros receptores de método

Um _receptor_ em Go consiste no argumento que foi definido numa declaração de método. Examine o código a seguir:

				
					
type Creature struct {

	Species string

}



func (&lt;^&gt;c Creature&lt;^&gt;) String() string {

	return c.Species

}

				
			

O receptor neste método é c Creature. Ele está declarando que a instância c é do tipo Creature e você fará referência àquele tipo através daquela variável da instância.

Assim como o comportamento das funções é diferente – considerando se você envia um argumento como um ponteiro ou um valor, os métodos também têm comportamentos diferentes. A grande diferença é que, se você definir um método com um receptor do valor, você não vai conseguir fazer alterações na instância daquele tipo no qual o método foi definido.

Haverá ocasiões em que você irá querer que seu método consiga atualizar a instância da variável que você estiver usando. Para permitir que isso ocorra, você vai querer transformar um ponteiro em receptor.

Vamos adicionar um método Reset ao nosso tipo Creature que definirá o campo Species para uma string vazia:

				
					
[label main.go]

package main



import "fmt"



type Creature struct {

	Species string

}



func (c Creature) Reset() {

	c.Species = ""

}



func main() {

	var creature Creature = Creature{Species: "shark"}



	fmt.Printf("1) %+v\n", creature)

	creature.Reset()

	fmt.Printf("2) %+v\n", creature)

}

				
			

Se executarmos o programa, teremos o seguinte resultado:

				
					
[secondary_label Output]

1) {Species:shark}

2) {Species:shark}

				
			

Embora tenhamos definido o valor de Species para uma string vazia no método Reset, note que, ao imprimimos o valor de nossa variável creature na função main, o valor permanece definido como shark. Isso acontece porque definimos o método Reset como tendo um receptor value. Isso significa que o método terá acesso apenas a uma _cópia_ da variável creature.

Se quisermos conseguir alterar a instância da variável creature nos métodos, precisaremos defini-los como tendo um receptor pointer:

				
					
[label main.go]

package main



import "fmt"



type Creature struct {

	Species string

}



func (c &lt;^&gt;*&lt;^&gt;Creature) Reset() {

	c.Species = ""

}



func main() {

	var creature Creature = Creature{Species: "shark"}



	fmt.Printf("1) %+v\n", creature)

	creature.Reset()

	fmt.Printf("2) %+v\n", creature)

}

				
			

Note que, dessa vez, adicionamos um asterisco (*) na frente do tipo Creature quando definimos o método Reset. Isso significa que a instância de Creature que é enviada para o método Reset passará a ser um ponteiro e, como tal, quando nós o alterarmos, isso irá afetar a instância original daquela variável.

				
					
[secondary_label Output]

1) {Species:shark}

2) {Species:}

				
			

O método Reset agora mudou o valor do campo Species.

Conclusão

Definir uma função ou método com um parâmetro passado por _valor_ ou passado por _referência_ irá afetar quais partes do seu programa conseguirão fazer alterações a outras partes. Controlar quando tal variável poderá ser alterada permitirá que você escreva softwares mais robustos e previsíveis. Agora que você aprendeu sobre ponteiros, você pode ver como eles são usados em interfaces.