Posted on: Written by: K-Sato
⚠️ This article was posted over 3 years ago. The information might be outdated. ⚠️

Table of Contents

Overview

In this post, I’ll mainly explain what pointers and structures are. Pointers and Structures can be very hard concepts to grasp for people who previously only learned script languages like Ruby, Python and so on.

Pointers

How to declare a pointer

In brief, a pointer is a value which points to the memory address of another value. A pointer can be declared using the * followed by the type of the stored value in Go.
The variable pointer1 is a pointer to an int type value and pointer2 is a pointer to a string type value in the following code.

package main

import "fmt"

func main() {
  var pointer1 *int
  var pointer2 *string

  fmt.Println(pointer1, pointer2) //=> nil nil // A pointer's zero value is `nil`
}

How to retrieve the memory address of a variable

In Go, you can use the & operator to retrieve the memory address of a variable. In the following code, &str represents the memory address of the variable str.

package main

import "fmt"

func main() {
  var str = "Go"
  fmt.Println(&str) //=> 0xc42000e1e0
}

How to use pointers

You can assign the memory address of a variable to a pointer just like the code below.

package main

import "fmt"

func main() {
  var pointer1 *int
  var pointer2 *string

  var str = "Go"
  var num = 10

  pointer1 = &num
  pointer2 = &str

  fmt.Println(pointer1, pointer2) //=> 0xc420016078 0xc42000e1e0
}

You can also declare a pointer more succinctly like the following code.

package main

import "fmt"

func main() {
  var num2 = 20
  pointer3 := &num2

  fmt.Println(pointer3) //=> 0xc420016080
}

pointer1 and pointer3 can only store the memory address of int type values and pointer2 can only store the memory address of a string type value respectively.

package main

import "fmt"

func main() {
	var pointer1 *int
	var str = "Go"

	pointer1 = &str //=> cannot use &str (type *string) as type *int in assignment
}

Accessing the value of a variable through its pointer

The * operator denotes the pointer’s underlying value. By using this feature, you can set, read and change the value of a variable through its pointer.

package main

import "fmt"

func main() {
  var pointer1 *int
  var num = 10

  pointer1 = &num

  fmt.Println(*pointer1) //=> 10 //read var1 through the pointer.
  *pointer1 = 20 //change the value of var1 through the pointer.
  fmt.Println(num) //=> 20 //the new value of num
}

Structs

The concept of structs in Go is kind of similar to the concept of classes in object-oriented programming languages. A struct is a typed collection of fields. They’re useful for grouping data together to form records.

How to declare a struct

You can declare a struct using the type and struct keywords.

package main

import  "fmt"

type person struct {
  firstName string
  age int
}

In the example above, person struct contains firstName and age as its fields.

Different ways of Struct Instantiation

There are multiple ways to instantiate a struct, I’ll demonstrate the following 4 methods here.

The var keyword and Dot Notation

A struct uses a . to access the values stored in fields.

package main

import "fmt"

type person struct {
  firstName string
  age int
}

func main() {
  var mike person
  mike.firstName = "Mike"
  mike.age = 20

  fmt.Println(mike.firstName, mike.age) //=> Mike 20
}

The var keyword and := operator

The following 2 sets of code illustrates struct instantiation using var and :=.

package main

import "fmt"

type person struct {
  firstName string
  age int
}

func main() {
  bob := person{ "Bob", 30 }

  fmt.Println(bob.firstName, bob.age) //=> Bob 30
}

You can also explicitly refer to a field and assign a value to it.

package main

import "fmt"

type person struct {
  firstName string
  age int
}

func main() {
  sam := person{ age: 40, firstName: "Sam" }

  fmt.Println(sam.firstName, sam.age) //=> Sam 40
}

Using the new keyword

The following code shows struct instantiation using the new keyword.

package main

import "fmt"

type person struct {
  firstName string
  age int
}

func main() {
  jen := new(person)

  jen.firstName = "Jennifer"
  jen.age = 10

  fmt.Println(jen.firstName, jen.age) //=> Jennifer 10
}

Structs and Pointers

Struct fields can be accessed through a struct pointer.

package main

import "fmt"

type person struct {
  firstName string
  age int
}

func main() {
  var mike person

  sPointer := &mike

  sPointer.firstName = "Mike"
  sPointer.age = 20
  fmt.Println(mike) //=> {Mike 20}
}

Methods

As I mentioned earlier, Go does not have classes. However, you can define methods on struct types.
A method is a function with a special receiver argument.

How to define a method

You can declare a method like the following code snippet.

func(receiver receiverType) nameOfFunction(arg) typeOfReturnValue {
   //content of the function
}

In the example below, intro method has a receiver of type person named named onePerson.

package main

import "fmt"

type person struct {
    firstName string
    age int
}

func(onePerson person) intro(arg string) string {
    return "Hello I'm" + " " + onePerson.firstName + " " + arg
}

func main() {
    mike := person{ "Mike", 20 }
    fmt.Println(mike.intro("!")) //=>Hello I'm Mike !
}

Pointer receivers

You can declare methods with pointer receivers. This means the receiverType has the literal syntax *Type for some type Type.
There are several differences between pointer receivers and normal value reveivers. One circumstance you should use pointer receivers over value receivers is when you want to change the state of the receiver in a method.
With pointer receivers, you can modify(read/write) the receiver in a method just like the code below.

package main

import "fmt"

type Num struct {
    x, y int
}

func (p Num) stay() {
    p.x = 10
    p.y = 20
}

func (p *Num) modify() {
    p.x = 10
    p.y = 20
}

func main() {
    numOne := Num {0, 0}
    numOne.stay()

    fmt.Println(numOne) //=> {0 0}

    numOne.modify()

    fmt.Println(numOne) //=> {10 20}
}

As you can see in the code above, The method stay() does not change the values of Num struct. On the other hand, The method modify() changes the values of Num struct.
If you want to know more about the differences of pointer receivers from value receivers, you can check here

Type Embedding

Go does not provide the typical, type-driven notion of sub-classing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.

package main

import "fmt"

type Person struct {
   firstName string
}

func (a Person) name() string{ //Person typed method
    return a.firstName
}

type User struct {
     Person
}

func (a User) name() string { //User typed method
    return a.firstName
}

func main(){
  bob := Person{"Bob"}
  mike := User{}
  mike.firstName = "Mike"

  fmt.Println(bob.name()) //=> Bob
  fmt.Println(mike.name()) //=> Mike
}

About the author

I am a web-developer based somewhere on earth. I primarily code in TypeScript, Go and Ruby at work. React, RoR and Gin are my go-to Frameworks.