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.
|
|
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
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
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
- When the channel is closed and all messages have been received, the
- Before returning the deferred statement will execute signaling the worker is done