Go Structs and Methods

Now it’s time to review OOP in Go.

Go OOP

Go only supports encapsulation, but it doesn’t support inheritance or polymorphism, which is not necessarily a bad thing, as it removes unnecessary confugions.

There’s no class in Go, there’s on struct.

Define a struct

Let’s declare a very common data structure treeNode with struct. It should have the following elements:

  • value
  • left, right, which are pointers
1
2
3
4
type treeNode struct {
value int
left, right, *treeNode
}

To create a treeNode type of data

1
2
3
func main(){
var root treeNode
}

Here root is a treeNode type data with value of 0, left and right pointing to nil. The outputs will be:

1
{0 <nil> <nil>}

To assign values, left and right to the root treeNode,

1
2
3
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}

Note left and right are pointers, so they need to use & to get the node address.

We could also create treeNode with new method.

1
root.right.left = new(treeNode)

See how simple and elegant to use Go pointers, we could use . directly after a pointer pointing to another pointer.

  • Use . to access members (which can be an address, a value or the struct itself)

Slices of Structs

We can build a slice of structs

1
2
3
4
5
6
nodes := []treeNode{
{value: 3},
{},
{6, nil, &root},
}
fmt.Println(nodes)

The outputs will be:

1
[{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc0000a6018}]

Methods

Go doesn’t have generator functions. We could use factory functions alternatively as generator functions.

1
2
3
func createNode(value int) *treeNode {
return &treeNode{value: value}
}

We could call the createNode function

1
2
3
4
5
6
7
8
9
10
func main() {
var root treeNode
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
root.left.right = createNode(2)

fmt.Println(root)
}

The outputs will be:

1
{3 0xc0000a6018 0xc0000a6030}

Go has automatic garbage collector, so we don’t need to know if the struct is on stack or heap.

Define struct method

Unlike other OOP languages, in Go the struct method is defined outside the struct block.

1
2
3
func (node treeNode) print(){
fmt.Print(node.value)
}

We need to pass node treeNode as the parameter, which is similar to self in Python or this in Java.

To invoke it, we can just call directly:

1
root.print()

The output will be

1
3

We can define a “setter”

1
2
3
func (node treeNode) setValue(value int){
node.value = value
}

Then invoke it:

1
2
3
root.right.left.print()
root.right.left.setValue(20)
root.right.left.print()

The outputs will be:

1
2
3
3
0
0

Because Go functions passes value not address, so the “setter” won’t work as expected. Instead, refactor it as

1
2
3
func (node *treeNode) setValue(value int){
node.value = value
}

Re-invoking the functions, the outputs will be:

1
2
3
3
0
20

In-Order Traverse

To traverse the tree:

1
2
3
4
5
6
7
8
func (node *treeNode) traverse(){
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}

Invoke traverse in main, we will get the outputs:

1
2
3
4
5
0
2
3
0
5

Passing-Value vs. Passing-Pointer

  • If we want to change the content, we must pass pointers
  • If the struct is too big, consider passing pointers as well
  • Be consistent, if passing pointers, keep passing pointers