Go Structs and the zero state value

Go Structs and the zero state value

I would call GoLang a data-oriented language, not an object-oriented language, although you can make some object-oriented patterns with it. For example, Golang doesn't have classes, inheritance and method overriding. Another thing it doesn't have is objects. Go has three classes of types:

  1. Built-in.
  2. Struct.
  3. Reference.

The struct type is the closest thing we have to an object from other languages such as Ruby or Python. We use structs when the built-in types (strings, numerics, booleans) are not enough to represent the mental model we want to achieve. A struct allows you to define the data that you need to describe a "thing" this could be a dog, a mobile phone, a user, etc.

Go works differently when instantiating a struct; for example, with Python, the most basic implementation of an object would be like this.

class Person:
  def __init__(self, name, age, music):
    self.name = name
    self.age = age
    self.favourite_music = music

The syntax to create an object is this:

person = Person("John", 36, ["rock", "punk", "classical"])

In case we want to only set the name, we will get the following error:

person = Person("John")
Traceback (most recent call last):
  File "<pyshell#45>", line 1, in <module>
    person = Person("John")
TypeError: Person.__init__() missing 2 required positional arguments: 'age' and 'music'

The same happens with Ruby; this is the class template:

class Person
  def initialize(name, age, music)
    @name = name
    @age = age
    @music = music
  end
end

person = Person.new("John", 36, ["rock", "punk", "classical"])

Let's recreate the error just passing "John".

person = Person.new("John")
(irb):28:in `initialize': wrong number of arguments (given 1, expected 3) (ArgumentError)
    from (irb):35:in `new'
    from (irb):35:in `<main>'
    from /Users/enriquesalceda/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /Users/enriquesalceda/.rbenv/versions/3.0.1/bin/irb:23:in `load'
    from /Users/enriquesalceda/.rbenv/versions/3.0.1/bin/irb:23:in `<main>'

Both languages will allow the creation of default values with a different syntax. However, Golang enables you to create a Struct without specifying the attributes at instantiation.

Let's say you have a similar data structure to the previous example in Golang and we make multiple instantiation combinations:

package main

import "fmt"

type Person struct {
    Name  string
    Age   int
    Music []string
}

func main() {
    personOne := Person{
        Name:  "Tom",
        Age:   20,
        Music: []string{"Rock", "Bossa Nova"},
    }

    personWithoutMusic := Person{
        Name: "Jane",
        Age:  20,
    }

    emptyPerson := Person{}

    fmt.Printf("%+v\n", personOne)
    fmt.Printf("%+v\n", personWithoutMusic)
    fmt.Printf("%+v\n", emptyPerson)
}

We created a person with all the required data, another without music and a completely empty person. This didn't raise an error, and we can see the following output.

{Name:Tom Age:20 Music:[Rock Bossa Nova]}
{Name:Jane Age:20 Music:[]}
{Name: Age:0 Music:[]}

As you can see, Go is not raising an error. So, why this is happening? As I said, I would consider Golang a data-oriented programming language and not an object-oriented language. Go is simple and does not implement a presence validation by default; Go lets you define those validations somewhere else if you want in a factory or the design pattern of your choice. Go will set every value to a zero state unless we specify the value for that attribute. Go cares about data integrity. Therefore those values will exist, and this is not free; those attributes having a zero state value have those available bytes in memory, and an empty Person struct still has the required space for a string, an integer and an array of strings in memory.

So, what if we want a constructor? That is not a difficult task; we can create a Person constructor as follows.

package main

import "fmt"

type Person struct {
    Name  string
    Age   int
    Music []string
}

func NewPerson(name string, age int, music []string) *Person {
    return &Person{
        Name:  name,
        Age:   age,
        Music: music,
    }
}

func main() {
    person := NewPerson("Mick", 78, []string{"rock", "blues", "jazz"})

    fmt.Printf("%+v\n", person)
}

Then, without the need of any specific validation the constructor will complain if do not pass any of the expected parameters.

import "fmt"

type Person struct {
    Name  string
    Age   int
    Music []string
}

func NewPerson(name string, age int, music []string) *Person {
    return &Person{
        Name:  name,
        Age:   age,
        Music: music,
    }
}

func main() {
    person := NewPerson("Mick")

    fmt.Printf("%+v\n", person)
}

This will raise a not enough arguments error

./prog.go:22:22: not enough arguments in call to NewPerson
    have (string)
    want (string, int, []string)

Go build failed.

Output:

./prog.go:22:22: not enough arguments in call to NewPerson
    have (string)
    want (string, int, []string)

Go build failed.