What is a pointer?
What is a pointer type?
How to create and use variables of pointer type.
What is the zero value of a pointer-type variable?
What is dereferencing?
What is the specificity of slices, maps, and channels?
Pointer
Memory address
Pointer type
Dereferencing
Referencing
A pointer is “a data item that specifies the location of another data item”
In a program, we are constantly storing and retrieving data. For instance, strings, numbers, complex structs... At the physical level, data are stored at specific addresses in memory. Pointers contain those memory addresses.
\nKeep in mind that a pointer variable, like any other variable, also has a memory address.
\n\nThere isn’t such thing as a unique pointer type. There are as many pointer types as there are types. A pointer type is the set of all pointers to variables of a given type1.
\nPointer types have the following syntax :
\n*BaseType
\nWhere BaseType
can be any type.
Let’s take some examples :
\n*int
denotes all pointers to variables of type int
.
*uint8
denotes all pointers to variables of type uint8
.
type User struct {\n ID string\n Username string\n}
\n*User
denotes all pointers to variables of type User
It can be created with the following syntax :
\nvar p *int
\nHere we create a variable p
of type *int
.*int
is a pointer type (which base type is int
).
Let’s create an integer variable named answer
.
var answer int = 42
\nNow we can assign a value to the variable p
:
p = &answer
\nWith the &
symbol we get the address of the variable answer
. Let’s print this address!
fmt.Println(p)\n// 0xc000012070
\n0xc000012070
is a hexadecimal number. You can spot that because it starts with 0x
. Memory addresses are often noted in the hexadecimal format. You could still express it with the binary format (with zeros and ones), but it’s not easy to read.
The zero value of pointer types is always nil
. In other words, a pointer that holds no address is equal to nil
.
A pointer variable holds an address to another variable. What if you want to retrieve the value behind the address? You can use the dereference operator *
.
Let’s take an example. We define a type struct Cart
:
type Cart struct {\n ID string\n Paid bool\n}
\nThen we create a variable cart
of type Cart
. We can take the address of this variable, but also follow the address:
With the operator *
you follow the address
With the operator &
you take the address
The dereference operator *
is the same symbol as the one used to denote a pointer type. *card
may denote a pointer type but also a dereferenced pointer variable. Analyze the usage context closely, and you will easily differentiate the two.
There is a panic that every Go programmers have encountered :
\npanic: runtime error: invalid memory address or nil pointer dereference\n[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1091507]
\nTo better understand it, we will try to reproduce it :
\n// pointer/nil-pointer-dereference/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n var myPointerVar *int\n fmt.Println(*myPointerVar)\n}
\nIn this listing, we have defined a pointer variable myPointerVar
. This is a variable of type *int
(pointers integers).
Then we are trying to dereference it. myPointerVar
variable holds a pointer that has not been initialized therefore the value of this pointer is nil
. The program will panic because we are trying to follow a nonexistent address! We are trying to go to the nil address. The nil address is something that does not exist.
Maps and channels are already references to the internal structure. Consequently, a function/method that accepts a map or a channel can modify it, even if the parameter is not a pointer type. Let’s take an example :
\n// pointer/maps-channels/main.go\n// ...\n\nfunc addElement(cities map[string]string) {\n cities["France"] = "Paris"\n}
\nThis function takes a map as input
It adds an entry to the map (key = “France”, value = “Paris”)
// pointer/maps-channels/main.go\npackage main\n\nimport "log"\n\nfunc main() {\n cities := make(map[string]string)\n addElement(cities)\n log.Println(cities)\n}
\nWe initialize a map named cities
Then the function addElement
is called
This program prints :
map[France:Paris]
\nWe will cover more extensively channels and maps in dedicated sections.
\n\nA slice is a collection of elements of the same type.
\nInternally a slice is a structure that has three fields :
\nA length
A capacity
A pointer to an internal array.
Here is an example slice EUcountries
:
package main\n\nimport "log"\n\nfunc main() {\n EUcountries := []string{"Austria", "Belgium", "Bulgaria"}\n log.Println(EUcountries)\n}
\n\n// pointer/slices-add-elements/main.go\npackage main\n\nimport "log"\n\nfunc main() {\n EUcountries := []string{"Austria", "Belgium", "Bulgaria"}\n addCountries(EUcountries)\n log.Println(EUcountries)\n}\n\nfunc addCountries(countries []string) {\n countries = append(countries, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...)\n}
\nThe function addCountries
takes a slice of string as parameter
It modifies the slice by appending strings to it with the builtin append
It appends to the slice the missing EU countries.
Question : In your opinion, what the following program outputs?
\n[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden]\n\n[Austria Belgium Bulgaria]
\nAnswer : This function outputs effectively :
\n[Austria Belgium Bulgaria]
\n\nThe function takes an element of type []string
as parameter
When the function is called a copy of the slice EUcountries
is done by the language
The function will get a copy of :
\nthe length
the capacity and
the pointer to the internal array.
Inside the function, the countries are effectively added
The length of the slice will grow,
The runtime will allocate a new internal array.
Let’s add a log inside the function to visualize it :
\nfunc addCountries(countries []string) {\n countries = append(countries, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...)\n log.Println(countries)\n}
\nThis log outputs :
\n[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden]
\n// pointer/slices-update-elements/main.go\npackage main\n\nimport (\n "log"\n "strings"\n)\n\nfunc main() {\n EUcountries := []string{"Austria", "Belgium", "Bulgaria"}\n upper(EUcountries)\n log.Println(EUcountries)\n}\n\nfunc upper(countries []string) {\n for k, _ := range countries {\n countries[k] = strings.ToUpper(countries[k])\n }\n}
\nupper
, that will put in uppercase every element of a slice of strings with strings.ToUpper
Question : In your opinion, what the following program outputs?
\n[AUSTRIA BELGIUM BULGARIA]\n\n[Austria Belgium Bulgaria]
\nAnswer : This function outputs effectively :
\n[AUSTRIA BELGIUM BULGARIA]
\n\nThe function upper gets a copy of the slice EUcountries
(like before)
Inside the function, we change the values of the slice elements countries[k] = strings.ToUpper(countries[k])
The slice copy has still a reference to the underlying array
We can modify it!
... but only the elements of the slice that are already in the slice.
When you pass a slice to a function, it gets a copy of the slice.
It does not mean that you cannot modify the slice.
You can just modify the elements already present in the slice.
With a pointer, you can modify the slice as expected :
\n// pointer/slices-add-elements-pointer/main.go\npackage main\n\nimport (\n "log"\n)\n\nfunc main() {\n EUcountries := []string{"Austria", "Belgium", "Bulgaria"}\n addCountries2(&EUcountries)\n log.Println(EUcountries)\n}\n\nfunc addCountries2(countriesPtr *[]string) {\n *countriesPtr = append(*countriesPtr, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...)\n}
\nThis program works as expected :
\n[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden]
\nThe function addCountries2
takes a pointer to a slice of strings (*[]string
) as parameter
The function append
is called with as first parameter *countriesPtr
(we dereference the pointer countriesPtr
)
The second parameter of append doesn’t change
The result is then affected to *countriesPtr
There is a shortcut that allows you to directly modify a variable of type struct without the dereferencing operator :
\ncart := Cart{\n ID: "115552221",\n CreatedDate: time.Now(),\n}\ncartPtr := &cart\ncartPtr.Items = []Item{\n {SKU: "154550", Quantity: 12},\n {SKU: "DTY8755", Quantity: 1},\n}\nlog.Println(cart.Items)\n// [{154550 12} {DTY8755 1}]
\ncart
is a variable of type Cart
cartPtr := &cart
: takes the address of the variable cart and stores it into cartPtr
.
With the variable cartPtr
we can directly modify the field Items
of the variable cart
There is an “automatic dereference” which is easier to write than the equivalent
(*cartPtr).Items = []Item{\n {SKU: "154550", Quantity: 12},\n {SKU: "DTY8755", Quantity: 1},\n}
\n(which works also, but is more verbose)
\n\nPointers are often used as receivers of methods. Let’s take an example with a Cat
type:
type Cat struct {\n Color string\n Age uint8\n Name string\n}
\nYou can define a method for this type with a pointer to a Cat
as receiver (*Cat
):
func (cat *Cat) Meow(){\n fmt.Println("Meooooow")\n}
\nThe method Meow
does nothing interesting; It just prints the string \"Meooooow\"
. Here we do not modify our receiver: we do not change the value of Name
for instance. Here is another method that will modify one of the cat attributes:
func (cat *Cat) Rename(newName string){\n cat.Name = newName\n}
\nThis method will change the name of the cat. Therefore, the pointer is useful because we modify one of the Cat struct fields.
\nOf course, if you do not want to use a pointer receiver, you can :
\nfunc (cat Cat) RenameV2(newName string){\n cat.Name = newName\n}
\nIn this example, the variable cat
is a copy. The receiver is named “a value receiver”.As a consequence, any modifications that you will do to the cat variable will be done on the cat copy :
// pointer/methods-pointer-receivers/main.go\npackage main\n\nimport "fmt"\n\ntype Cat struct {\n Color string\n Age uint8\n Name string\n}\n\nfunc (cat *Cat) Meow() {\n fmt.Println("Meooooow")\n}\n\nfunc (cat *Cat) Rename(newName string) {\n cat.Name = newName\n}\n\nfunc (cat Cat) RenameV2(newName string) {\n cat.Name = newName\n}\n\nfunc main() {\n cat := Cat{Color: "blue", Age: 8, Name: "Milow"}\n cat.Rename("Bob")\n fmt.Println(cat.Name)\n // Bob\n\n cat.RenameV2("Ben")\n fmt.Println(cat.Name)\n // Bob\n}
\nOn the first line of the main function, we create a new cat. Its name is \"Milow\"
.
When we call the RenameV2
method, which has a value receiver, the name of the original cat will not change; it will stay stable.
When we call Rename
, the cat’s Name
field value will change.
Use a pointer receiver when :
\nYour struct is heavy (otherwise, Go will make a copy of it)
You want to modify the receiver (for instance, you want to change the name field of a struct variable)
Your struct contains a synchronization primitive (like sync.Mutex) field. If you use a value receiver, it will also copy the mutex, making it useless and leading to synchronization errors.
Use a value receiver
\nWhen your struct is small
When you do not intend to modify the receiver
When the receiver is a map, a func, a chan, a slice, a string, or an interface value (because internally it’s already a pointer)
When your other receivers are pointers
How to denote the type of a variable holding a pointer to a Product
?
What is the zero value of a pointer type?
What does “dereferencing” mean?
How to dereference a pointer?
Fill in the blanks. A ____ is internally a pointer to an ____.
True or false. When I want to modify a map in a function, my function needs to accept a pointer to the map as parameter. I also need to return the modified map.
How to denote the type of a variable holding a pointer to a Product
?
What is the zero value of a pointer type?
\nWhat does “dereferencing” mean?
\nA pointer is an address to a memory location where data is stored.
When we dereference a pointer, we can access the data stored in memory at this address.
How to dereference a pointer?
\n*
Fill in the blanks. A ____ is internally a pointer to an ____.
\nTrue or false. When I want to modify a map in a function, my function needs to accept a pointer to the map as parameter. I also need to return the modified map.
\nFalse.
You can just accept a map (not a pointer to a map)
No need also to return the modified map.
Pointers are addresses to data
The type *T
denotes the set of all pointers to variables of the type T
.
To create a pointer variable, you can use the operator &
. It will take the address of a variable
userId := 12546584\np := &userId
\nusername
is a variable of type int
*int
*int
denotes all the pointers to variables of type int
A function with a parameter/receiver of pointer type can modify the value that the pointer points to.
Maps and channels are “reference types”
Functions/Methods that accept maps or channels can modify the values stored internally in those two data structures (no need to pass a pointer to a map or a pointer to a channel)
A slice holds a reference to an array internally; any function/method that accepts a slice can modify the slice elements.
When you want to modify a slice length and capacity in a function, you should pass to that function a pointer to a slice (*[]string
)
Dereferencing allow you to access and modify the value stored at the pointer address.
To dereference a pointer, use the operator *
userId := 12546584\np := &userId\n*p = 4\nlog.Println(userId)
\np
is a pointer.
With *p
we dereference the pointer p
We modify the value of userId
with the instruction *p = 4
At the end of the code snippet, the value of userId
is 4 (and no longer 12546584)
When you have a pointer to a struct, you can access a field directly with your pointer variable (without using the dereference operator)
\ntype Cart struct {\n ID string\n}\nvar cart Cart\ncartPtr := &cart
\nInstead of writing : (*cartPtr).ID = \"1234\"
You can directly write : cartPtr.Items = \"1234\"
The cart
variable is effectively modified
See https://golang.org/ref/spec#Pointer_types↩︎
Previous
\n\t\t\t\t\t\t\t\t\tMethods
\n\t\t\t\t\t\t\t\tNext
\n\t\t\t\t\t\t\t\t\tInterfaces
\n\t\t\t\t\t\t\t\t