5 minute read

It has been three years now since I started coding in Go. I think it is time for a bit of retrospective and I would like to enumerate some of the aspects I found most “weird” during my Golang learning process.

Before we start and just in case you fell into this post by accident, some context:

What is Go?

Go is a programming language developed by Google. Similar to C, it is compiled, statically typed with garbage collection, memory safety and with a special focus on concurrency and networking. The language is deeply used at Google to develop from services to complete solutions. Actually, Kubernetes is developed using Go.

Why did I start using Go?

I started designing a smart orchestrator for multi-cloud applications. The target platform was Kubernetes, so it seemed to be the perfect match for the project.

Go features newbies (and not so newbies) may find weird

Humans are wired for prejudice. When we start coding in a programming language we do not feel comfortable. We always point out those features that annoy us the most. Sometimes it is difficult to put these features into the corresponding context and understanding the reasons why they were designed in such way. I enumerate some of the features I found weirder when starting my Go experience.

In my case, I have experience in a bunch of languages: C, C++, Java, Scala, Python, R (if we can consider R a language) among others. In general, I did not find my transition difficult. The syntax is straight forward, and the language is not very verbose (in principle). However, I found some frustration with the following features.

Unnecessary imports and variables Go forces the code to be minimalistic. This implies that unnused imports and variables trigger compilation errors. For example:

import (
	"fmt"
	"os" //not used
)

func main() {
	fmt.Println("Hola")
}

The compiler returns

imported and not used: "os"

Iterating collections The range function used to iterate collections returns two values. The first is the position of the entry in the collection and the second value contains the entry value itself.

	x := [4]string{"one","two","three","four"}
	for i, entry := range(x) {
	   fmt.Printf("Element at position %d is %s\n", i, entry)
	}

This is very handy because you have in every iteration the two most common values you may use in your loops. However, they are not always required. This means that you will do something like:

	x := [4]string{"one","two","three","four"}
	for i, entry := range(x) {
	   fmt.Printf("Element %s\n", entry)
	}

Which returns an error during compilation:

i declared but not used

Or even worse, you will skip the i variable like this:

    x := [4]string{"one","two","three","four"}
    for entry := range(x) {
       fmt.Printf("Element %s\n", entry)
    }

Which can be really confusing because it returns the position value in a variable we expected to be the entry value.

Element %!s(int=0)
Element %!s(int=1)
Element %!s(int=2)
Element %!s(int=3)

We simply have to indicate an unnused variable i.

	x := [4]string{"one","two","three","four"}
	for _, entry := range(x) {
	   fmt.Printf("Element %s\n", entry)
	}

Attributes visibility Attributes are visible if they start with an uppercase letter. If no, they are private. It is simple. However, I do regularly forget about this resulting in stupid errors.

type Message struct {
 Text string // This is public
 text string // This is private
}

What happened to overloaded methods? If you come from the Java world you are probably used to overload methods. This is, for a method we can have several signatures. Well… Golang has no method overloading.

What happened to inheritance? There is no inheritance. Just that. You can do some workaround like the one described here, but we cannot really say that is inheritance.

What about interfaces? There are interfaces. They can be defined as a collection of method signatures. However, they are “weird” in the sense you use them in other languages. Why? Because you do not programatically indicate that your struct implements an interface (something like class A implements interface I). Your struct fulfils an interface if it has the methods enumerated by the interface. It is easier to understand with an example.

package main

import (
	"fmt"
)

type Speaker interface {
    SayYourName() string
    SayHello(b Speaker) string
}

type HappySpeaker struct {}

func(hs HappySpeaker) SayYourName() string {
    return "Happy"
}

func(hs HappySpeaker) SayHello(b Speaker) string {
    return fmt.Sprintf("Hello %s!",b.SayYourName())
}

type AngrySpeaker struct {}

func(as AngrySpeaker) SayYourName() string {
    return "Angry"
}

func(as AngrySpeaker) SayHello(b Speaker) string {
    return fmt.Sprintf("I'm not going to say hello to %s!",b.SayYourName())
}

func main() {
	// We have two different structs
	happy := HappySpeaker{}
	angry := AngrySpeaker{}
	// they can say their names
	fmt.Println(happy.SayYourName())
	fmt.Println(angry.SayYourName())
	
	// But they are also speakers
	fmt.Println(happy.SayHello(angry))
	fmt.Println(angry.SayHello(happy))
	
	// This is also valid
	var mrSpeaker Speaker = happy
	fmt.Println(mrSpeaker.SayHello(angry))
}

As you can imagine this has larger implications when coding. Interfaces in Go are a much deeper topic for discussion and you can find a lot of examples defining pros and cons for this approach.

What about constructors? There are no constructors like the ones you may find in any object oriented language. Structs definition resembles a lot the one used in C. With one potential issue: you can skip attributes setting when instantiating a new struct. In the following code halfMessage1and halfMessage2have unset attributes.

package main

import (
	"fmt"
)

type Message struct {
	MsgA string
	MsgB string
}

func(m Message) SayIt() {
  fmt.Printf("[%s] - [%s]\n",m.MsgA, m.MsgB)
}


func main() {
	
	fullMessage1 := Message{"hello","bye"}
	fullMessage2 := Message{MsgA: "hello", MsgB: "bye"}
	
	halfMessage1 := Message{"hello",""}
	halfMessage2 := Message{MsgA: "hello"}
	
	emptyMessage := Message{}
	
	fullMessage1.SayIt()
	fullMessage2.SayIt()
	halfMessage1.SayIt()
	halfMessage2.SayIt()	
	emptyMessage.SayIt()		
}

The output is:

[hello] - [bye]
[hello] - [bye]
[hello] - []
[hello] - []
[] - []

This is always a potential issue because you can have methods expecting values to be set. A way to mitigate this is to define your own static “constructors”.

package main

import (
	"fmt"
)

type Message struct {
	MsgA string
	MsgB string
}

func(m Message) SayIt() {
  fmt.Printf("[%s] - [%s]\n",m.MsgA, m.MsgB)
}

func NewMessage(msgA string, msgB string) *Message{
  if len(msgA) * len(msgB) == 0 {
     return nil
  } 
  return &Message{MsgA: msgA, MsgB: msgB}
}

func main() {
   // A correct message
   msg1 := NewMessage("hello","bye")	
   if msg1 != nil {
      msg1.SayIt()
   } else {
      fmt.Println("There was an error")
   }
   	
	
   // An incorrect message
   msg2 := NewMessage("","")
   
   if msg2 != nil {
      msg2.SayIt()
   } else {
      fmt.Println("There was an error")
   }
}

Summary

This is a small excerpt of all the potential aspects you have to consider when conding in Go. I would like to hear from your experiences? What are the Go features you find the most weird?