Red Green Repeat Adventures of a Spec Driven Junkie

Go Structs

Background

I have been playing around with Go Lang recently and have been really enjoying it. Go feels like a “modernized C”, still minimal like C, but with modern programming langauge features to make it easier to use, especially after using more current languages.

One thing I learned from a friend is how to use: “Go Structs” to prototype functions and create function instances. Looking up structs, they kind of look like C structs to me, a kind of customized array.

He was so nice and wrote out a simple version of a program using Go Structs. I tried to look for more info, but couldn’t find it… until now.

Go Lang book describes these as methods, which looks like the official name for this is method.

After using them, I can see why my friend told me to: “Use Go Structs”.

Go Structs Methods feel like a prototype, it describes the function, and instance is the use of the prototype.

They are useful as they:

  • they isolate naming of functions
  • methods can get values from the struct
  • values on the struct are settable by methods

Motivation

I want to document some of my learnings in Go, especially around Go Structs. I couldn’t find much in terms of documentation, and if my friend didn’t write a bunch of Go code for me showing how he uses Go Structs, I would still write C styled Go code, which isn’t so pretty in Go.

Requirements

If you want to follow along, these are instructions to setup a vagrant enironment using virualbox.

Setting up environment

  • Install vagrant
  • clone repo: $ git clone https://github.com/a-leung/go_structs.git

In terminal, run these commands:

$ cd go_structs
$ vagrant up

Test

To run the automated tests, run these commands in a terminal:

$ cd go_structs
$ vagrant up
$ vagrant ssh
...
$ go test -v go_structs

Normal Functions

As a demonstration, the following is a normal function in Go:

func Hello() string {
  return "Hello"
}

To use this, call the function like any other function, in the main program file:

// hello.go
package main

import "fmt"

func Hello() string {
  return "Hello"
}

func main() {
  fmt.Println(Hello())
}

It’s pretty straight forward. Calling the Hello() function is possible anywhere, from any function or method.

Test

To test the code in the article, I am using the following basic test file, even though it does not test the functions directly. I want to just run: $ go test hello

// hello_test.go
package main

import "testing"

func TestHello(t *testing.T) {
    if true == false {
        t.Error("true is not false?!")
    }
}

Go Method or “Go Struct”

Now with Go Methods, to define AnotherHello() method:

func (s *Struct) AnotherHello() string {
  return "Another Hello"
}

But, trying to call the method will result in an error:

// hello.go
package main

import "fmt"

func Hello() string {
  return "Hello"
}

func main() {
  fmt.Println(Hello())
  fmt.Println(AnotherHello())
}
go test hello
# hello
go/src/hello/another2.go:3: undefined: Struct
go/src/hello/hello.go:13: undefined: AnotherHello
FAIL	hello [build failed]

Looks like just adding the (s *Struct) prevents any call to it that is not associated with the Struct type.

Let’s fix that by adding Struct type definition:

package main

type Struct struct {}

func (s *Struct) AnotherHello() string {
    return "Another Hello"
}
go test hello
# hello
./hello.go:13: undefined: AnotherHello
FAIL	hello [build failed]

Ok, that still fails. Well, looks like the way to call AnotherHello is to instantiate the struct:

package main

import "fmt"

func hello() string {
    return "Hello"
}

func main() {
    fmt.Println(hello())

    another := &Struct{}

    fmt.Println(another.AnotherHello())
}
go test hello
ok      hello	0.002s

So, without the line: another := &Struct{}, AnotherHello() is not called. To call the function to, prefix with the variable instantiating, which in this case is: another.

If the instantiation was: superStruct := &Struct{}, to call AnotherHello() would be: superStruct.AnotherHello().

Get Values from Struct from Method

As this is a struct, they can store anything. So the method can even access items inside. Let’s say the struct & method is setup as:

type Greeter struct {
    string GreetingMessage
}

func (g *Greeter) SayHello() string {
  return g.GreetingMessage
}

Now, wherever initializing the struct, it will set values inside the struct:

// greeting.go
package main

type Greeter struct {
    GreetingMessage string
}

func (g *Greeter) SayHello() string {
    return g.GreetingMessage
}

// greeting_test.go
package main

import "testing"

func TestGreeting(t *testing.T) {
    english := &Greeter {
        GreetingMessage: "Good day",
    }

    if (english.SayHello() != "Good day") {
        t.Error("not getting value from struct")
    }

    french := &Greeter {
        GreetingMessage: "Bon Jour",
    }

    if (french.SayHello() == "Good day") {
        t.Error("not getting value from struct")
    }
}

So, one can have a prototype method which can be purely implementation and only upon usage, set the values.

In this case, one instance for French, another for English, another for Japanese, they all use the same prototype method: SayHello() from the Greeter struct.

Set Struct Values from Method

Not only can methods read struct values, but struct methods can set struct values:

// dice.go
package main

type Dice struct {
    Seed int
}

func (d *Dice) SetSeed(value int) {
    d.Seed = value
}

func (d *Dice) GetSeed() int {
    return d.Seed
}

// dice_test.go
package main

import "testing"

func TestDice(t *testing.T) {
    dice := &Dice{}

    dice.SetSeed(1)

    if dice.GetSeed() != 1 {
        t.Error("Issue with set seed")
    }

    dice.SetSeed(2)

    if dice.GetSeed() == 1 {
        t.Error("Issue with set seed")
    }
}

Method functions can alter values in the struct. In the example above, the dice can have it’s random seed value altered at a regular interval. If there are more than one each can have their own seed or even have matching seeds when a player is on a “hot streak”.

Conclusion

Coming from C, Go feels familiar. C Structs are a specialized array to contain any type of primitive.

Go Structs share a similar property, but because attaching them to methods, they are more powerful. Go Structs can get and set values within the struct, that allows more flexibility while keeping run time code and configuration code separate.

I find this a great way to balance concrete implementation with run-time configuration for a method:

  • Run-time configuration can be setup once.
  • Method parameters list can be the variable parameters within the run-time, not any setup configuration.
  • No need to have ‘globals’ to track initialized method values.

With Go Structs, I start to think of functional components that are self contained.