What I don’t like in Golang

Fabrizio Guglielmino
5 min readDec 13, 2022

I wrote a small article, few days ago, comparing Go with Rust, it was a very high level overview of some specific features, for people evaluating to use one of them.

The opportunity of the beginning

Now, being in that phase, where you are using a language but you aren’t still proficient, it’s huge opportunity. Using a language where you are comfortable is like driving with autopilot: you don’t notice details anymore. You use paradigms you learned without a critical view on them.
This is not a criticism: this is the stage where you are most efficient, and efficiency is due a set of mechanisms that come automatically, without having to think about them.

On the other hand, when you start coding with a new language, you are more exposed to experimentation, looking for solutions and paradigms that there’ll bring you to the “proficient stage”, when you’ll care less on finding solutions and more on using the language to make what you need.

There is a valuable effect when in this stage: you can spot language weaknesses that’ll be less evident later. Of course, this is true especially when you are learning the second (or third, or fourth, ….) language, having a background on how some problems can be addressed, coming from previous experiences.

What I didn’t like

After the introduction, it’s time to share what I didn’t like in my learning journey with Go. I want to be crystal clear: what I’m about to write is my opinion, coming from my background and it can be totally different for you (btw, some of the problems are shared with other languages).

  1. Shadowing

Variable shadowing occurs in Go when a new variable is declared with the same name as an existing variable in the same scope. This can lead to confusion and make it difficult to understand the code, as it is not always clear which variable is being referred to. Let’s see a very simple example:

package main

import "fmt"

var x int = 5

func main() {
fmt.Println(x) // prints 5

var x int = 10
fmt.Println(x) // prints 10
}

In this case is pretty obvious what value will be printed but let’s try to articulate the problem a little bit more .

var x int = 5

func main() {

var x int = 10
fmt.Println(x)

if x > 5 {
var x int = 15
fmt.Println(x)
}
}

See what I mean? It can become quite unreadable very easily.

2. No function overloading

This is not a topic where Go is alone, there are plenty of languages without function overloading, but Go is recent and it was designed to be simple and easy. Here, I think that considering function overload a source of complexity was an error.

Let’s take an example in pseudo code

int Add(int a, int b)
{
return a + b;
}

float Add(float a, float b)
{
return a + b;
}

The idea is straightforward: if you call Addwith two integers the first function is used and, clear enough, the second one using float.

In Go it wold be something like

AddInt(a int, b int) int {
return a + b
}

AddFloat(a float64, b float64) float64 {
return a + b
}

The difference is subtle but the impact is not: in Go you (the developer) must choose which function use given the context, while in the previous example is automatically chosen by the compiler.

3. Lacks of generics

This is partially true, generics has been introduced in Go starting from version 1.18. But the damage has been done: a lot of code was written before generics were introduced, so there’ll be a mix of code with and without them, for long time.

Generics can overcome the function overload in some circumstances, for example the previous example about adding numbers could be rewritten as following:

package main

import "fmt"

func AddIntOrFloat[T int | float64](a T, b T) T{
return a + b
}

func main() {
fmt.Printf("Add int %d\n", AddIntOrFloat[int](10, 12))
fmt.Printf("Add float %f", AddIntOrFloat[float64](10.45, 12.233))
}

AddIntOrFloat is a function using generics, the square brackets before the round brackets is the generic syntax to define a type T that can be both int of float64. In this case is matter of “taste” but, having used generics in other languages, I find the square brackets syntax quite confusing, let’s see a similar example in C#:

public T Sum<T>(T x, T y)
{
dynamic result = x + y;
return result;
}

int a = 3;
int b = 4;
int c = Sum<int>(a, b);

Maybe because this way to use generics is more common (C#, TypeScript, Java, C++, …) or simply because I used it for a long time, whatever the reason, I find this syntax more readable.

4. Error handling

If you worked a little bit with Go, or simply read some code, the following code should sound familiar.

func anUntrustableFunc() (int, error) {
...
}

func main() {
res, err := anUntrustableFunc()

if err != nil {
panic(err)
}

...
}

What is wrong with it? Honestly? Nothing. Anyway, in some circumstances, it leads to lack of clearness. Let’s see an example

func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return err
}
defer r.Close()

w, err := os.Create(dst)
if err != nil {
return err
}
defer w.Close()

if _, err := io.Copy(w, r); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
}

See? It doesn’t seem following the DRY principle too much.

As I wrote, its mainly matter of taste, but shorter code is most readable because short is better (well, not always, to be honest 😉 ).

5. Implicit access modifiers

This is the last point but, actually, I’m quite disappointed by this feature.

It’s highly probable that you saw something like this

public int pubVar;  
private int privVar;

As you know for sure the public variable is visible outside the current scope, while the private one is not, trivial, isn’t it? Well, Go designers thought that this wasn’t trivial enough and so created a even simplest rule

var PubVar int     // this is public
var privVar int // this is private

Did you spot the difference? Yes, your guess is right: case changes the visibility. Now, there is nothing overly serious about this but I see it as an unwanted limitation. Pretend for a moment that you need to create a symbol (function, variable, type …) that represent the iPad, and you need to export it

var IPad int // exported variable 

Funny eh?

Summing up

Don’t take me wrong: I like Go. There are many amazing features: goroutines, channels, defer and many others. What I wanted to show here, as a experienced developer learning Go, are some features I disliked. This is personal taste, and also a way to get feedback from You. Maybe, after using Go in some projects, I’ll change my mind on some of these features.

Keep coding!

--

--