My 5 favourite features of Go and how to use them

go_five_features1.jpg

Ever since I joined Pusher a little over a year ago, I’ve had the opportunity to work on some amazing projects and learn new programming languages. One of the languages I came across was Go. I’d seen Go code before and it looked nasty. Just another case of judging a book by its cover as, \[…\]

Introduction

Ever since I joined Pusher a little over a year ago, I’ve had the opportunity to work on some amazing projects and learn new programming languages. One of the languages I came across was Go.

I’d seen Go code before and it looked nasty. Just another case of judging a book by its cover as, even though it’s not the nicest to look at, learning and using it was a pleasure.

So here’s a few things I love about Go and some of the reasons it has become my go-to language (no pun intended). I’d love to get your take on the languages you use, is Go one of them? Do you think it should be? Let me know at @pusher.

Concurrency

Go offers some great concurrency primitives and makes it extremely easy to implement a concurrent system. Go supports this at the language level and concurrency is a first class citizen. The fundamental unit for this in Go is a go routine.

Go routines are cheap, lightweight threads of execution. Spawning a go routine is as simple as adding the go keyword before a function. For example:

1import “fmt”
2
3package main
4
5func doSomething(str sting) {
6     for i := 1; i <= 3; i++ {
7          fmt.Printf(“%s: %d”, str, i)
8     }
9}
10
11func main() {
12    // calling this function the normal way
13    doSomething(“Hello”)
14
15    // Running it inside a go routine
16    go doSomething(“World”)
17
18    go func() {
19        fmt.Print(“Go routines are awesome”)
20    }()
21}

Running this will cause the output to be interleaved since the second call to doSomething() is made inside a Go routine followed by an anonymous function call inside another Go routine.

1Hello: 1
2Hello: 2
3Hello: 3
4World: 1
5Go routines are awesome
6World: 2
7World: 3

Pretty cool right? However, doing something in a Go routine means they need to be able communicate with other routines. To accomplish this, Go provides channels.

Channels are conduits that allow two concurrent Go routines to talk to each other by sending and receiving values. The <- syntax can be used to send a value into a Go routine, while the <-channel syntax can be used to read something from a channel, like so:

1package main
2
3import “fmt”
4
5func main() {
6    // make a new channel
7    messages := make(chan string)
8    go func() {
9        // write to channel
10        message <- “Hello World!”
11    }()
12
13    // read from channel
14    message :=  <-messages
15
16   fmt.Printf(“I got the message %s”, message)
17}

It’s that simple!

One thing to understand here is that when reading from the channel, we block till we get a message. The sender and receiver, both have to be ready. That is, we can’t send till there is a corresponding receive for that channel. One might wonder how channels can be made unblocking. This is accomplished using buffered channels. Buffered channels are channels which are initialized with a buffer

1chan := make(chan int, 10)

Here, we create a new channel and define a capacity for the channel, which means it can only accommodate 10 values. Now, we can send values into the channel without blocking till the buffer is full and read from the channel without blocking till the buffer is empty.

Go also provides select which lets us wait on several Go routines. It is idiomatic to use in concurrent Go and is a very powerful primitive.

1package main
2
3import “time”
4
5func main() {
6    chan1 := make(chan int)
7    chan2 := make(chan int)
8
9    go func() {
10        chan1 <- 1
11        time.Sleep(1 * time.Second)
12    }()
13
14    go func() {
15        chan2 <- 1
16        time.Sleep(2 * time.Second)
17    }()
18
19    for i := 0; i < 2; i++ {
20        select {
21             case v := <-chan1:
22                 fmt.Printf(“Value: %d”, v)
23             case v := <-chan2:
24                fmt.Printf(“Value: %d”, v)
25        }
26    }
27
28}

It is trivial to see what is happening here. We simply wait on the values of two Go routines inside the select block.

I found it extremely easy to reason about these concepts and use them in practice. When planning to write something, it is easy to put together a skeleton fairly quickly since there are only a few primitives to use, but solve a wide variety of problems.

Simplicity and Consistency

Go is a relatively simple language and was designed with a very minimalistic approach. I found it easy to pick up and get started with. The standard library contains most things including a web server!

Small features of the language help achieve this. For example – If the function name in a Go package starts with an uppercase letter, it is exported while all functions with lower case names are not. Go does not support generics, although it is strongly and statically typed. While this is debatable, it was done to keep the language simple and to avoid complexity. The standard library is exemplary and the packages are consistent.

The language does restrict the way you can do some things, but this compromise in flexibility means more simplicity. It might mean writing a bit more code but there are some clear solutions. Consistency is enforced by the language and it goes a long way in contributing to readability and maintainability.

There are idiomatic ways of doing things in Go and these come directly from the Go team based on the language design. effective go showcases some of these. go-fmt allows formatting Go code and is rooted in the same approach.

Go is object oriented

Coming from other languages, it may seem like Go is not object-oriented. It does not provide the class keyword and has no support for inheritance. This might seem bizarre. However, Go’s replacement for classes are structs. A struct may have any number of properties and methods defined on them.

For example:

1package worker
2
3import “fmt”
4
5type Person struct {
6    Name string
7    Age int
8}
9
10func (p *Person) Talk(phrase string) {
11    return fmt.Sprintf(“%s says: %s”, p.Name, phrase)
12}

Here Person is a struct type having Name and Age as fields. Talk is a method defined on Person that prints something. We can use this as –

1person := &Person{“Vivan”, 22}
2person.Talk(“Go is awesome”)

This will print Vivan says: Go is awesome

It is very similar to instantiating a new object and calling some methods on it. While Go takes a different approach, I think it’s easier to reason about structs. They are a collection of typed fields and methods that allow you to interact with data in some way.

Go also has interfaces. An explicit declaration of the implementation is not required. It’s just a case of implementing the methods declared in the interface into the struct type.

1type Actions interface {
2    Walk(distance float)
3    Run(distance float)
4}
5
6func (p *Person) Walk(distance float) {
7    // implementation
8}
9
10type Animal struct {
11    Name string
12    Species string
13}
14
15func (a *Animal) Walk(distance string) {
16    // implementation here
17}

We defined an Actions interface, and had Person and Animal implement it. However, we did not have to specify if we are actually implementing that interface.

Like I mentioned before, there is no inheritance. Go goes for composition over inheritance. This is achieved by embedding structs and interfaces. This allows types to borrow functionality from other structs and interfaces.

The best example of this would be from the Reader, Writer and ReadWriter interfaces from the io package in Go.

1type Reader interface {
2    Read(p []byte) (n int, err error)
3}
4
5type Writer interface {
6    Write(p []byte) (n int, err error)
7}
8
9type ReadWriter interface {
10    Reader
11    Writer
12}

The ReadWriter interface embeds the Reader and Writer, which means all the methods available in the Reader and Writer interfaces are available to the ReadWriter interface!

It’s a simpler broken down approach to OO and it works! More traditional OO language users may frown, but I do think breaking components down into struct types and composing them where required is elegant.

The Compiler

Go’s compiler is super fast. It is easily possible to compile a large Go program within a few seconds. The fact that the language syntax is so simple means that compilation is much quicker. The language was designed to be easily parseable without a symbol table.

Go is also statically linked which means that the compiler invokes a linker in the last step that resolves all library references. This means we’d get one binary executable after compiling a Go program with no external dependencies. While this means the executable itself is bigger in size, it is faster and also more portable. Go also compiles to native machine code negating the need for environments such as JVM.

Pointers

I thought I’d never have to use pointers after my university days, learning C. Go aims to provide a modern equivalent of C in some areas and has brought back pointers. Most modern languages do not provide pointers, however I do feel pointers help solve a lot of common issues and play a far more important role when it comes to memory layout and building low level system tools.

Pointers solve problems that often make you scratch your head or when you expect something to be updated to a new value, but it doesn’t. However, using pointers in a wrong way could have bad implications.

Here’s an example of how to use pointers in Go:

1func setX(x int, value int) {
2    x = value
3}
4
5func main() {
6    x := 1
7    setX(x, 5)
8    fmt.Printf(“Value of x is %d”, x) 
9}

This will still print 1 since the value of x has not changed. When we pass x to setX, we pass it by value. Its value is copied over to another memory location, while the original is left intact.

However, when using pointers

1func setX(x *int, value int) {
2   *x = value
3}
4
5func main() {
6    x := 1
7    setX(&x, 5)
8    fmt.Printf(“Value of x is %d”, x)
9}

This will now print 5 since we passed a reference to x as the argument to setX.

Having written a lot of Go over the past few months, I’ve embraced pointers and find them very useful in a lot of situations!

Let us know how you get on with Go!

These are just a few of my thoughts on Go. I’ve focused on the positives here but of course there’s areas that need work. The Go community is growing larger by the day and the great people at Google are actively working to make Go even better.

So if you’ve never tried Go before, give it a try and let me know what you think. I’m also interested to hear about new programming languages so if you’ve discovered a hidden gem, drop me a line. Leave a comment below or find me on Twitter @vivangkumar.