5 Mistakes I’ve Made in Go

Ali Josie
Ali Josie
Oct 25 · 5 min read

To err is human, to forgive divine.
— Alexander Pope

Image for post
Image for post

These are mistakes that I’ve made writing Go. Although these might not cause any sort of error but they can potentially affect the software.

1. Inside Loop

1.1 Using reference to loop iterator variable

Using reference to loop iterator variable

The result will be:

Values: 3 3 3
Addresses: 0xc000014188 0xc000014188 0xc000014188

As you can see all elements in out slice are 3. It is actually easy to explain why this did happen: in every iteration we append the address of v to the out slice. As mentioned before v is a single variable which takes new values in each iteration. So as you can see in second line of the output, addresses are the same and all of them are pointing to the same value.

The simple fix is to copy the loop iterator variable into a new variable:

Solution

The new output:

Values: 1 2 3
Addresses: 0xc0000b6010 0xc0000b6018 0xc0000b6020

The same issue can be find the loop iteration variable is being used in a Goroutine.

Output will be:

3 3 3

It can be fixed using the very same solution mentioned above. Note that without running the function with Goroutine, the code runs as expected.

1.2 Calling WaitGroup.Wait in loop

Another blocking bug: Wait in loop

1.3 Using defer in loop

Using defer in loop

In above example, if you use line 8 instead of line 10, next iteration can not hold mutex lock because the lock is already in use and the loop blocks forever.

If you really need to use defer inside loop you might wanna entrust another function to do the work.

entrust a new func to do the work

But, sometimes using defer in loop may become handy. So you really need to know what you are doing.

Go suffers no fools

2. Sending into an unguaranteed channel

Sending into unguaranteed channel

Let’s check above code. The doReq function creates a child Goroutine at line 4 to handle a request which is a common practice in Go server programs. The child Goroutine executes do function and sends result back to the parent through channel ch at line 6. The child will block at line 6 until the parent receives result from ch at line 9. Meanwhile, the parent will block at select until either when the child sends result to ch (line 9) or when a timeout happens (line 11). If timeout happens earlier, the parent will return from doReq func at line 12, and no one else can receive result from ch any more, this results the child being blocked forever. The fix is to change ch from an unbuffered channel to a buffered one, so that the child Goroutine can always send the result even when the parent has exit. Another fix can be using a select statement with empty default case at line 6 so if no Goroutine receiving the ch, default will happen. Although this solution might not work always.

...
select {
case ch <- result:
default:
}
...

3. Not using interfaces

Among many interfaces, io.Reader and io.Writer might be the most beloved ones.

type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}

These interfaces can be very powerful. Let’s assume you are going to write an object into a file, so you defined a Save method:

func (o *obj) Save(file os.File) error

What if you need to write into a http.ResponseWriter next day? you don’t want to define a new method. Do you? So use io.Writer.

func (o *obj) Save(w io.Writer) error

Also an important note you should know is that always ask for behaviors you are going to use. In above example, asking for an io.ReadWriteCloser can work as well but it’s not a best practice when the only method you are going to use is Write. The bigger the interface, the weaker the abstraction.

So most of the time you better stay with behaviors instead of concrete type.

4. Bad ordered struct

It seems like both types have the same size of 21 bytes, but the result shows something totally different . Compiling code using GOARCH=amd64 , the BadOrderedPerson type allocates 32 bytes while OrderedPerson type does 24 bytes. Why? Well, the reason is Data structure alignment. In 64 bit architecture memory allocates consecutive packet of 8 bytes. Padding need to be added can be calculate by:

padding = (align - (offset mod align)) mod align
aligned = offset + padding
= offset + ((align - (offset mod align)) mod align)
What actually happen to struct in compiler

It can lead to a performance issue when you have a big frequently used type. But don’t worry, you don’t have to take care of all your structs manually. Using maligned you can easily check your code for this issue.

5. Not using race detector in test

$ go test -race pkg    // to test the package
$ go run -race pkg.go // to run the source file
$ go build -race // to build the package
$ go install -race pkg // to install the package

When race detector enabled, compiler will record when and how the memory was accessed within the code, while the runtime watches for unsynchronized accesses to shared variables.

When the a data race has been found, race detector prints a report which contains stack traces for conflicting accesses. Here is an example:

WARNING: DATA RACE
Read by goroutine 185:
net.(*pollServer).AddFD()
src/net/fd_unix.go:89 +0x398
net.(*pollServer).WaitWrite()
src/net/fd_unix.go:247 +0x45
net.(*netFD).Write()
src/net/fd_unix.go:540 +0x4d4
net.(*conn).Write()
src/net/net.go:129 +0x101
net.func·060()
src/net/timeout_test.go:603 +0xaf
Previous write by goroutine 184:
net.setWriteDeadline()
src/net/sockopt_posix.go:135 +0xdf
net.setDeadline()
src/net/sockopt_posix.go:144 +0x9c
net.(*conn).SetDeadline()
src/net/net.go:161 +0xe3
net.func·061()
src/net/timeout_test.go:616 +0x3ed
Goroutine 185 (running) created at:
net.func·061()
src/net/timeout_test.go:609 +0x288
Goroutine 184 (running) created at:
net.TestProlongTimeout()
src/net/timeout_test.go:618 +0x298
testing.tRunner()
src/testing/testing.go:301 +0xe8

Last words

The Startup

Medium's largest active publication, followed by +729K people. Follow to join our community.

Ali Josie

Written by

Ali Josie

Yet Another Software Engineer https://www.linkedin.com/in/alijosie/

The Startup

Medium's largest active publication, followed by +729K people. Follow to join our community.

Ali Josie

Written by

Ali Josie

Yet Another Software Engineer https://www.linkedin.com/in/alijosie/

The Startup

Medium's largest active publication, followed by +729K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store