Pointers are one of the most important building blocks of writing good code. In this post, we are going to explore pointers, what they are, and how can they be used in Go to write excellent code.
1. What is a pointer?
A pointer is a variable that stores the address it points to. A pointer of a specific type can only point to that type.
2. Golang Pointer syntax
The syntax for the pointers is really simple. Here is the syntax of the declaration of pointers in Go.
var ptr *type
var ptrint *int // pointer to an int
The zero-value of the pointer is nil.
3. Pointers Initialization in Go
The pointers of a type are initialized using the address-of(&) operator on a subject of that specific type. Here is the way to do it.
package main
import (
"fmt"
)
func main() {
var q int = 42
var p *int // declare the pointer
p = &q // initialize the pointer
fmt.Println(p) // 0x40e020
}
4. Go Pointers dereferencing
Dereferencing a pointer means getting the value inside the address the pointer holds. If we have a memory address, we can dereference the pointer to that memory address to get the value inside it. Here is the same example showing the dereference operation using the star(*) operator.
package main
import (
"fmt"
)
func main() {
var q int = 42
var p *int
p = &q
fmt.Println(p) // 0x40e020
fmt.Println(*p) // 42
}
5. Pointer to pointer in Golang
A pointer variable can store even a pointers address since a pointer is also a variable just like others. So, we can point to a pointer and create levels of indirection. These levels of indirection can sometimes create unnecessary confusion so be wary when using it.
package main
import (
"fmt"
)
func main() {
i := 64
j := &i // j is pointer to an int
k := &j // k is pointer to a pointer to an int (pointer to another pointer)
fmt.Println(i) // 64
fmt.Println(j) // 0x40e020
fmt.Println(*j) // 64 (value inside that address)
fmt.Println(k) // 0x40c138
fmt.Println(*k) // 0x40e020 (address of j)
}
6. Pointer to interfaces
A pointer can point to anything even an interface. When an empty interface is used it returns nil as a value.
package main
import (
"fmt"
)
func main() {
var a interface{}
b := &a
fmt.Println(b) // 0x40c138
fmt.Println(*b) // <nil>
}
Here is an example using an interface with pointers.
package main
import (
"fmt"
)
// declare interface
type Bird interface{
fly()
}
type B struct{
name string
}
// implement it
func (b B)fly() {
fmt.Println("Flying...")
}
func main() {
var a Bird = B{"Peacock"}
b := &a
fmt.Println(b) // 0x40c138
fmt.Println(*b) // {Peacock}
}
Here “a” is a struct of the type Bird which is then used for an interface type as you can see. This is polymorphism in action. Go allows polymorphism using interfaces. So, you can see pointers to a struct or an interface is an essential tool in Go.
7. Pointers as function arguments
Pointers can be used in function arguments just like value. It has some advantages over using values directly. It is a very efficient way to pass large objects to function. So, it is a great optimization to have it.
package main
import (
"fmt"
)
// declare pointer as argument
func f(a *int) {
fmt.Println(*a)
}
func main() {
var a int = 42
// pass the address
f(&a) // 42
}
Using large objects can slow down execution time, here is an example of passing a pointer to a struct. This is an efficient way to handle large objects.
package main
import (
"fmt"
)
type Human struct {
name string
age int
place string
}
func f(h *Human) {
fmt.Println("The user", (*h).name, "is", (*h).age, "years old and he is from", (*h).place)
}
func main() {
john := Human{"John", 36, "Las Vegas"}
f(&john) // The user John is 36 years old and he is from Las Vegas
}
Be careful when dereferencing a struct. If you use it like *structname.field1
then it will throw an error. The correct way to do it is (*structname).field1
.
Using pointers inside functions makes the value mutable unless its const. So, whenever we want to change a value we should use a pointer to that as function argument and then do necessary modifications.
8. The “new” function in Go
The new function in Go returns a pointer to a type.
package main
import (
"fmt"
)
func main() {
ptri := new(int)
*ptri = 67
fmt.Println(ptri) // 0x40e020
fmt.Println(*ptri) // 67
}
9. Returning a pointer from a function
Pointers of any type can be returned from a function just like any other value. It is really simple. Instead of returning the value directly we simply return the address of that value.
package main
import (
"fmt"
)
func p() *int { // specify return type as pointer
v := 101
// return the address
return &v
}
func main() {
n := p()
fmt.Println(n) // 0x40e020
fmt.Println(*n) // 101
}
10. Pointers to a function
Pointers to a function work implicitly in Go. That means we don’t need to declare it as a pointer.
package main
import (
"fmt"
)
func main() {
f := func() {
fmt.Println("a function")
}
pf := f
pf() // a function
}
11. Things to keep in mind while using Pointers in Go
Go doesn’t allow pointer arithmetic. So, we cannot do anything like unary increment or decrement as we can do in C/C++.
We may want to use a pointer to an array but there is a better option for it. And that’s slices. Slices are much more versatile than a pointer to an array. The code is concise and makes our lives a lot easier. So, use slices whenever possible.