5 Golang Features I Wished Were Different!
Nik L.
Posted on December 27, 2023
More articles you can read:
- Using Golang to Build a Real-Time Notification System - A Step-by-Step Notification System Design Guide
- JSON is Slower. Here Are Its 4 Faster Alternatives
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
}
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)
}
}
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;
}
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
}
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))
}
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);
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)
}
// ...
}
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
}
}
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
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
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
Posted on December 27, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.