Go Proverbs Illustrated
Rob Pike, in a recent talk, shared a set of insightful principles in a hope to explain the Go language and a few common patterns in go programs using a set of simple, poetic and pithy proverbs
These profound set of elegant truths become evident as I practice more Go. I wrote this post to illustrate some of my experiences and learnings along the way.
Examples illustrated are primarily from uiprogress, uilive, uitable and go-store
The bigger the interface, the weaker the abstraction
The concept behind an Interface is to allow re-usability by abstracting an object’s behavior into a simple contract. Although, it is not exclusive to Go, it has been widely adopted by Go programmers because of the fact that Go interfaces generally tend to be small. Often times, limited to one or two methods.
I recently published uilive, a library for updating terminal output in real-time. To write, the user sends an array of bytes to writer’s Write([]byte)
method. Since the uilive’s Writer implements io.Writer’s Write method, the user can send this writer to any object that accepts io.Writer
, like fmt.Fprintf.
An example would be:
The small size of the io.Writer
interface allows for a stronger abstraction and wider adoption.
Make the zero value useful
Zero values can greatly simplify the API. For example, when using bytes.Buffer, the user just can declare and use it with out initialization (play).
In cases where zero values are impractical, package defaults can be used to simplify the API. For uiprogress, a library I wrote for rendering progress bars in terminal applications, using a DefaultProgress simplified the API in a way that the user can be productive with just four lines of code. This pattern is also used in net/http, like in http.ListenAndServe(...)
.
Not quite the zero value but a slightly related topic is the value of zero storage - the empty struct, one of my favorite data types. An empty struct, in essence, is a struct type that has no fields, no data and consumes no storage.
I tend to use this when communicating signals between go routines. The Listen method for uilive works this way. For example:
The below example demonstrates the zero storage property of an empty struct (play).
Dave Cheney explores this in depth in this post about using an empty struct in channels.
Channels orchestrate; mutexes serialize
Besides rendering progress bars, uiprogress can also be used as a general tracker. Making it ideal for tracking progress of work fanned out to a lots of go routines. It comes with an atomic counter Incr() that increments the current value by 1. During this increment operation, it is essential that no other operation mutate the count to inorder to ensure acccuracy. A RWMutex is used here to its such atomicity.
The implementation for func (b *Bar) Incr()
looks something like this:
In the above code, b.mutex.Lock()
will lock the cycle, during which b.current++
is being performed and b.mutex.Unlock()
will release the lock once the counter is incremented.
A little copying is better than a little dependency
In uiprogress and uitable, I format strings quite a bit. Usual tendency would be be to have a common strutil package and share that with these two libraries. Instead, I chose to copy the methods I need for these libraries. If you notice, the strutil
package has almost the same functions in uiprogress and uitable
I found that copying a dependency into the library often times makes it easier to maintain, especially with different versions.
Syscall must always be guarded with build tags
In uilive, inorder to make live updates, I had to clear out the current contents of the screen and write the buffer on those erased lines. POSIX uses control characters and windows works differently. The implementation for Flush
method calls clearLines()
, looks some thing like:
Guarded by build tags, the implemenation for writer_posix.go is something like:
And writer_windows.go has something like:
Build tags in this case not only gaurd, but actually simplify the implementation for different systems.
Reflection is never clear & interface{} says nothing
Sometime back, I wrote a datastore library for Redis that uses reflection heavily. This library continues to haunt me. I would avoid reflection and empty interface as much as possible, as you can see in the code below example is confusing (even for me, being the author):
Thank you for reading. Hope you found this post useful. Please feel free to leave a comment below or reach out to me twitter, if you’d like to get in touch.