5 Golang Features I Wished Were Different!

nikl

Nik L.

Posted on December 27, 2023

5 Golang Features I Wished Were Different!

More articles you can read:


As I delve into the world of Go, I find myself compelled to share some aspects that didn't quite align with my preferences. It's crucial to note that the following reflections are entirely subjective, stemming from my individual background. Furthermore, it's entirely plausible that your experience might differ, as some of the challenges I encountered are not exclusive to Go but are shared across various programming languages.

Shadowing Woes

One issue that made a significant impact on my learning journey in Go is the concept of variable shadowing. This phenomenon occurs when a new variable is declared with the same name as an existing variable within the same scope. This practice can introduce confusion and impede code comprehension, creating uncertainty about which variable is being referenced. Let's examine a straightforward example to illustrate this concern:

package main

import "fmt"

var x int = 5

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

    var x int = 10
    fmt.Println(x) // prints 10
}
Enter fullscreen mode Exit fullscreen mode

While the output in this specific instance might be evident, the problem intensifies when the code becomes more intricate, as showcased below:

var x int = 5

func main() {
    var x int = 10
    fmt.Println(x)

    if x > 5 {
        var x int = 15
        fmt.Println(x)
    }
}
Enter fullscreen mode Exit fullscreen mode

As seen here, readability can quickly diminish, making the code less transparent and potentially prone to errors.

Absence of Function Overloading

A notable absence in Go, which it shares with several other languages, is the lack of support for function overloading. While Go's design philosophy emphasizes simplicity, I contend that dismissing function overloading as a source of complexity might be an oversight. To elucidate this point, let's consider a pseudo-code example:

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

float Add(float a, float b)
{
    return a + b;
}
Enter fullscreen mode Exit fullscreen mode

In this scenario, the compiler automatically selects the appropriate function based on the input types. However, in Go, developers must explicitly choose the function based on the context:

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

func AddFloat(a float64, b float64) float64 {
    return a + b
}
Enter fullscreen mode Exit fullscreen mode

While the distinction may seem subtle, the consequential impact lies in the hands of the developer who must make explicit choices based on context, a departure from the automatic resolution provided by the compiler in other languages.

3. Absence of Generics

While it's true that Go has introduced generics starting from version 1.18, the repercussions of this implementation are noteworthy. A significant volume of code predates the introduction of generics, resulting in a prolonged coexistence of code both with and without this feature.

Generics offer a potential remedy for function overloading in specific scenarios. For instance, revisiting the earlier example of adding numbers, we can reimagine it using generics:

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))
}
Enter fullscreen mode Exit fullscreen mode

In this function utilizing generics, the square brackets preceding the round brackets denote the generic syntax, defining a type T that can be either int or float64. While this is a matter of personal preference, having worked with generics in other languages, I find the square brackets syntax somewhat perplexing. A comparison with a similar example in C# further highlights this distinction:

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);
Enter fullscreen mode Exit fullscreen mode

The more conventional usage of generics in languages like C#, TypeScript, Java, and C++ might contribute to a more intuitive syntax.

4. Challenges with Error Handling

For those acquainted with Go or have perused some code, the following pattern might ring familiar:

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

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

  if err != nil {
     panic(err)
  }

  // ...
}
Enter fullscreen mode Exit fullscreen mode

While there's nothing inherently wrong with this approach, in certain scenarios, it can lead to a lack of clarity. Consider the following example related to file copying:

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
   }
}
Enter fullscreen mode Exit fullscreen mode

This example, while functional, might be criticized for not adhering to the DRY (Don't Repeat Yourself) principle, favoring shorter code for improved readability.

5. Implicit Access Modifiers

The last point of contention revolves around implicit access modifiers, a feature that has left me somewhat disheartened. In conventional languages, explicit keywords like "public" and "private" elucidate the visibility of variables. However, Go designers opted for an even simpler rule:

var PubVar int     // this is public
var privVar int    // this is private
Enter fullscreen mode Exit fullscreen mode

The subtle distinction lies in the case, where the capitalization determines visibility. While this may not be a critical issue, it introduces an unwanted limitation. Consider a scenario where you need to create a symbol representing an iPad, and you want to export it:

var IPad int // exported variable
Enter fullscreen mode Exit fullscreen mode

The case change determining visibility might be perceived as a less intuitive choice.

Summing Up

Let me be clear: I have an appreciation for Go. It boasts numerous commendable features, including goroutines, channels, defer, and more. What I've aimed to convey here, as an experienced developer delving into Go, are specific features that didn't align with my preferences. This is a matter of personal taste and also a way to open a dialogue for feedback. Perhaps, after further immersion in Go through various projects, my stance on some of these features may evolve.

Keep coding!


Similar to this, I run a developer-centric community on Slack. Where we discuss these kinds of topics, implementations, integrations, some truth bombs, lunatic chats, virtual meets, and everything that will help a developer remain sane ;) Afterall, too much knowledge can be dangerous too.

I'm inviting you to join our free community, take part in discussions, and share your freaking experience & expertise. You can fill out this form, and a Slack invite will ring your email in a few days. We have amazing folks from some of the great companies (Atlassian, Gong, Scaler etc), and you wouldn't wanna miss interacting with them. Invite Form

💖 💪 🙅 🚩
nikl
Nik L.

Posted on December 27, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related