7 Golang Features You Might Find Weird
A retrospective on 3 years of writing Go

Humans are wired for prejudice. When we start coding in a programming language we don’t feel comfortable with, we always point out the features that annoy us the most.
Sometimes it’s difficult to put these features into the corresponding context and understand the reasons why they were designed. This doesn’t necessarily mean these features are wrong or incorrectly designed. They’re part of a learning process and are gradually incorporated into our programming skills.
In this article, I’ll enumerate some of the features (seven, in this case) that I found to be the most troublesome/weird while learning Golang.
This is a personal and completely subjective article. The following list is only a small excerpt I’ve chosen without any particular criteria. To give the reader some context, I have experience in a bunch of languages: C, C++, Java, Scala, Python, R (if we can consider R a language), among others — and nearly 20 years of experience coding my stuff.
In general, I found Go to be a language with a smooth learning curve. Probably, this is because it has a clearly defined purpose that eliminates some features that’d imply a more complex syntax. Anyway, this is my list.
1. Unnecessary Imports and Variables
Go forces the code to be minimalist
It implies that unused 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"
2. Iterating Collections
The range function used to iterate collections returns two values
The first is the position of the entry in the collection. 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’re not always required. You’ll probably do something like:
x := [4]string{"one","two","three","four"}
for i, entry := range(x) {
fmt.Printf("Element %s\n", entry)
}
This returns an error during compilation:
i declared but not used
Or even worse, you’ll skip the i
variable, like this:
x := [4]string{"one","two","three","four"}
for entry := range(x) {
fmt.Printf("Element %s\n", entry)
}
It can be confusing because it returns the position value in a variable 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 unused variable i
.
x := [4]string{"one","two","three","four"}
for _, entry := range(x) {
fmt.Printf("Element %s\n", entry)
}
3. Attributes Visibility
Attributes are visible if they start with an uppercase letter
If not, they’re private. It’s 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
}
4. What Happened to Overloaded Methods?
There aren’t any overload methods
If you come from the Java world, you’re probably used to overload methods. With overload methods, in a method, we can have several signatures. Well … Golang has no method overloading.
5. What Happened to Inheritance?
There’s no inheritance
That’s all. You can do some workarounds, like the one described here, but I can’t say this is really inheritance.
6. What About Interfaces?
There are interfaces
They can be defined as a collection of method signatures. However, they’re weird in the sense that you use them in other languages.
Why? Because you don’t programmatically indicate that your struct implements an interface (something like class A
implements interface I
). Your struct fulfills an interface if it has the methods enumerated by the interface. It’s easier to understand with an example.
package mainimport (
"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 implications when coding. Interfaces in Go are a much deeper topic for discussion, and you can find a lot of examples discussing the pros and cons.
7. What About Constructors?
You can skip the attributes set when instantiating a new struct
There are no constructors like the ones you may find in any object-oriented language. Struct definition resembles the one used in C a lot. With one potential issue: You can skip the attributes set when instantiating a new struct. In the following code halfMessage1
and halfMessage2
have unset attributes.
package mainimport (
"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 static constructors.
package mainimport (
"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 sampling of all the potential things to consider when coding in Go. I’d like to hear from your experiences. What are the Go features you find the weirdest?