Into to Go

Reading time ~5 minutes

What is Go? Why use Go?

Go is a statically typed, complied programming language developed at Google and released in 2003.

Go is very fast (~30x faster than python). One of the reasons Go is so much faster than python is that Go is compiled into machine code and run directly on the CPU while Python is compiled into bytecode and run on a VM. Go has great concurrency support built into the language. It makes creating and running concurrent code extremely easy compared to other languages. Go also has managed memory so no malloc/free business. This makes Go much easier to work with and makes code less prone to errors. Its standard library and toolchain are excellent. The compiler is very fast and even very large projects typically compile in seconds. There are no complicated Makefiles to deal with.

It is also very easy to add dependencies with the go get command.

Hello World

This wouldn’t be an real intro without a hello world.

1
2
3
4
5
6
7
8
9
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello World")
}

The first line of every go file must be the package name. The main package is special in go in that the main function in the main package is the entrypoint to code execution. The import block specifies each package we will be using. The fmt package is a built-in package for formatting and printing strings. Each function in go is defined by func. When we run this program we get the following output:

$ go run main.go
Hello World

Go is very opinionated

Go is a language that has very specific ways things must be done. For example, code will fail to compile if there are imported packages that are not used or variables that are defined and not used. This is ultimately beneficial for writing and maintaining good code. Code is easier to read when there are no unused variables and it also helps prevent bugs introduced by typos. Disallowing unused imports speeds up compilation.

Go also solves the code style debate by having a specific style built into the language tooling. By running go fmt, code will be formatted for you keeping all developers on the project on the same page.

Code directories are directly tied to package names and dependencies are placed in specific locations.

Type assignment

Types can be assigned explicitly:

var a int = 5

In this example the type is specified directly. Or implicitly:

a := 5

In the example above, the type of the variable is inferred based on the value being assigned. Go has built-in numeric types (such as int, int64, float, float64), booleans, and strings.

Interfaces and Structs

Interfaces are a collection of method signatures

type animal interface {
  say() string
}

Structs can implement an interface. In order to implement an interface a struct must contain all the methods on the interface along with the correct input and output types.

type dog struct {}

func (d dog) say() string {
  return "bark"
}

Go Concurrency

Go Concurrency" Concurrency in Go is handled by goroutines. Calling a function with the go prefix starts the function in a new goroutine. Mutex locks are built into the language, but for many cases its not required to manage locking manually. Go also has nice tools like race condition testing built into the test suite.

Channels

Go channels are another import component to concurrency in Go. Channels are used for sending and receiving data between different goroutines.

Here’s an example of channel creation.

// Make a channel that can store up to 50 int values
ch := make(chan int, 50)

A channel is created for ints than can hold up to 50 values.

To send and receive data on a channel the syntax looks like:

ch <- v    // Send data to the channel
v := <-ch  // Read from a channel and assign to a variable

Channels can also be closed to stop receivers from reading. Once a channel is closed, no more values can be sent to the channel. Attempting to do so will result in a Go panic.

close(ch)

Defer

Go allows running an action later in execution, usually for cleanup. Defer is similar in to having a finally block in Python code. Defer runs before a function returns. For example, if we wanted to write to a file and ensure we close it, we could to the following.

func writeStuff() {
    f := createFile("/tmp/somefile")
    defer closeFile(f)
    writeFile(f)
}

Putting it all together

Go Channels" For this example, I need to explain one more concept. In Go a WaitGroup is a set of actions with members than can be waited on to complete execution. You can add members to the WaitGroup using WaitGroup.Add(1). When an individual member is done with execution, it can signal to the WaitGroup that it is done via WaitGroup.Done(). To block a goroutine while we wait on all members of the WaitGroup to complete, we can run WaitGroup.Wait().

In this example, we are doing the following:

  • Create a channel to hold 20 strings
  • Create 4 workers and add them to a WaitGroup
  • Add 20 messages of “Hello World” to our channel, then close the channel
  • Wait for all workers to complete
  • Each worker will grab messages from the channel
    • When the channel is closed and all messages have been received, the for loop will exit
  • Before returning the deferred statement will execute signaling the worker is done
comments powered by Disqus