Google's Go, aka Golang, is an open source programming language that makes it easy to build simple, reliable, and efficient software. It’s part of the programming language lineage that started with Tony Hoare’s Communicating Sequential Processes, and it includes Occam, Erlang, Newsqueak, and Limbo.
While somewhat C-like, Go follows in the lineage of languages such as Erlang that implement lightweight concurrency; thus, Go is really good for systems that need massive scale. Go implements functional programming in an elegant manner, within a strongly typed, garbage-collected language. And though relatively young, Go has been battle-tested on enormous projects.
In the following guide, we’ll demonstrate some of the differentiating features of the language and its tools, including its extremely lightweight concurrency. The project currently has more than 500 contributors, led by Rob Pike, a distinguished engineer at Google, who worked at Bell Labs as a member of the Unix team and co-created Plan 9 and Inferno.
Gophers, unite!
Install Go
To install the Go language compiler and tools, first browse to golang.org and check the version at the bottom of the page. Then check your installed Go version from a command line:
$ go version
go version go1.8 darwin/amd64
If Go is not found or the official version is newer, then click on the Download Go link on the Golang home page, click again to download the featured binary download for your system, and follow the installation instructions.
Another alternative for Go language and tools installation is to use the ActiveState distribution.
Configure your Go environment
Go programmers typically keep all their code in one workspace, with bin, pkg, and src folders. Within each folder, the projects typically have paths that relate to Git repositories. For example, I keep all my Go language code under ~/work, and set my GOPATH
environment variable to $HOME/work
. The path to my "hello.go" source code folder is $GOPATH/src/github.com/meheller/hello
.
I also add the GOPATH/bin directory to my path, for convenience in running Go programs from any directory:
export GOPATH=$HOME/work
export PATH=$PATH:$(go env GOPATH)/bin
The Go language utilities will install in GOPATH
by default, so putting the GOPATH/bin directory on the path also makes it easier for you and Go-aware editors and IDEs to find them. You can install most of the utilities with $go get <repo-path>
once you know which ones you need and their repository paths. The repositories are usually easy to find with a Google search. In some cases, an editor plugin for Go will install the utilities (such as gocode) automatically.
If you don't set it yourself, GOPATH
defaults to $HOME/go
on Unix and MacOS and %USERPROFILE%/go
on Windows.
Use a Go-aware editor or IDE
As I discussed in "The best Go language IDEs and editors," a Go-aware editing and development environment can boost your productivity significantly. In that article I recommended Gogland as a desktop IDE, Visual Studio Code with the vscode-go plugin (shown below) as a desktop editor, and Cloud9 as a cloud IDE.
Slices
The Go language extends the idea of arrays with slices. A slice points to an array of values and includes a length. []T
is a slice with elements of type T. In the exercise below, we use slices of slices of unsigned bytes to hold the pixels of an image we generate; package main
is where programs start running. The import
statement is an extended version of the include
statement in C and C++; here we get the pic
file from a Mercurial repository. The :=
syntax declares and initializes a variable, and the compiler infers a type whenever it can. Also, make
is used to create slices and some other types. A for..range
loop is the equivalent of the for..in
loop on C#.
Maps
The Go map
statement maps keys to values. As with slice
, you create a map
with make
, not new
. In the example below, we are mapping string keys to integer values. Here we demonstrate inserting, updating, deleting, and testing for map
elements. As we'll see in a later example, you can't depend on the order or iteration over maps.
The program below prints:
The value: 42 The value: 48 The value: 0 The value: 0 Present? false
Structs and methods
The Go language lacks classes but has a struct
, which is a sequence of named elements, called fields, each of which has a name and a type. A method
is a function with a receiver. A method declaration binds an identifier, the method name, to a method, and it associates the method with the receiver's base type. In this example we declare a Vertex struct
to contain two floating point fields, X
and Y
, and a method Abs
. Fields that begin with uppercase letters are public; fields that begin with lowercase letters are private. Fields and methods are addressable through the dot notation; *
and &
signify pointers, as in C. The program below prints 5
.
Interfaces
An interface type is defined by a set of methods. A value of interface type can hold any value that implements those methods. In this example, we define an interface Abser
and a variable a
of type Abser
. Note that the assignments in lines 17 and 18 work, but the assignment in line 22 does not even compile. The Abs
method of Vertex
, which we saw in the previous example, has a pointer to Vertex
type for its receiver, so a *Vertex
implements Abser
, but a Vertex
does not.
Switch
The switch
statement in Go is similar to the switch
statement in other C-like languages, except that the case
statements can be types or expressions in addition to simple values, and the cases automatically break unless they end with fallthrough
statements. The cases are evaluated in the order they are defined.
Goroutines
Goroutines are, to a rough approximation, extremely lightweight threads, in the spirit of Tony Hoare’s Communicating Sequential Processes. Line 16 in the sample below calls the say
function asynchronously, while line 17 calls the say
function synchronously. Goroutines, channels, and select
statements form the core of Go’s highly scalable concurrency, one of the strongest selling points of the language. The language also has conventional synchronization objects, but they are rarely needed. The program below outputs:
hello world hello world hello world hello world hello
Channels
Channels in Go provide a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type. The value of an uninitialized channel is nil. In line 16 of the example below, we create a bidirectional channel of integers. We could also make unidirectional sending <-c
and receiving c<-
channels. In lines 17 and 18 we call sum
asynchronously with slices of the first and second half of a
. In line 19, the integer variables x
and y
receive the two sums from the channel. In line 7, the underscore _
, the blank identifier, means to ignore the first result value from the for..range
loop, which is the index. The program output is 17 -5 12
.
Range and close
A sender can close
a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression. A loop
for i := range c
receives values from the channel repeatedly until it is closed. The cap
of the channel is the capacity, which is the size of the buffer in the channel, set as the optional second argument when you make a channel, as in line 17. Note the compact form of the assignment statements in the fibonacci
function. The program output is the first 10 values of the Fibonacci series, 0 through 34.
Select
A select
statement chooses which of a set of possible send
or receive
operations will proceed. It looks similar to a switch
statement but with all the cases referring to communication operations. A select
blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple cases are ready.
Here the main
function calls the fibonacci
function with two unbuffered channels: one for results and one for a quit
signal. The fibonacci
function uses a select
statement to wait on both channels. The anonymous, asynchronous go
function that starts at line 21 waits to receive values at line 23, then prints them. After 10 values, it sets the quit
channel so that the fibonacci
function knows to stop.
Concurrency patterns, example 1
In this example, we use select
to create a fan-in goroutine that combines two input channels of string, input1
and input2
, into one unbuffered output channel, c
. The select
statement allows fanIn
to listen to both input channels simultaneously and relay whichever is ready to the output channel. It doesn’t matter that both cases use the same temporary variable name to hold the string from its respective input channel. The example is from Rob Pike’s 2012 talk on Go Concurrency Patterns.
Concurrency patterns, example 2
The code below implements a parallel search of the internet, sort of like what Google actually does. To begin with, replicas …Search
is a variadic parameter to the function; both Search
and Result
are types defined elsewhere.
The caller passes N search server functions to the First
function, which creates a channel c
for results and defines a function to query the i
th server and saves it in searchReplica
. Then First
calls searchReplica
asynchronously for all N servers, always returning the answer on channel c
, and returns the first result to come back from the N servers. The example is from Rob Pike’s 2012 talk on Go Concurrency Patterns.
The net/http package
The Go net/http package provides HTTP client and server implementations. This example implements a simple web server that returns the contents of the directory /usr/share/doc to a web client.
The example does not work properly in the golang.org online environment, but run on a Mac command line it returns the following to a web browser asking for http://localhost:8080/:
bash/
ccid/
cups/
groff/
ntp/
postfix/
Package template
The Go html/template package implements data-driven templates for generating HTML output that is safe against code injection. Without all of the escaping added by the html/template package, the example could have produced a runnable JavaScript string, Hello, <script>alert('you have been pwned')</script>!
.
Don't depend on order of iteration over maps
Maps, which we introduced earlier, use hash tables under the covers. To iterate over the entries in a map, you use the range
keyword. While you might expect the iteration to occur either in sorted order or in the order of insertion, that simply isn't how hash tables work.
If you want the output to be in insertion order, then you need to keep track of the keys as they are inserted, typically in an array, then use the array range to drive the retrieval of the map elements. If you want the output in sorted order, then sort the array keys:
import "sort"
var m map[int]string
var keys []int
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
fmt.Println("Key:", k, "Value:", m[k])
}
(The code above was shamelessly stolen from Nathan LeClaire, who in turn shamelessly stole it from Andrew Gerrand.)
Combine Go with code generators
The go build
command is quick and efficient, but it isn't make
or cmake
: It only deals with go
source files and is not a general-purpose build tool. While go generate
still isn’t a general-purpose build tool, it does help you automate code generation from within go
source files yet not resort to makefiles.
There are two pieces to this: the go generate
command-line tool and special comment directives (see the top line in the example below) inside Go language source files that control generation. Together they can help reduce the amount of copying and pasting you have to do to create boilerplate code.
Rob Pike's original blog post on this feature (from which the example comes) initially talks about using Yacc for Go (now called goyacc), but not many of us actually write parser generator grammars. More often, we hoi polloi write simple templates. For example, later in the same blog post Pike talks about using stringer combined with go generate
to automatically generate String methods for enumerated types. In another blog post, Kel Cecil describes using the Go text/template facility in a custom Go command-line app, combined with go generate
, to automatically generate calls to the GamesDB web service.
What can you do with go generate
?
Test in parallel
Most Go programmers know about the go test
command and the Go testing
package, but not all Gophers realize they can be used to perform fairly sophisticated actions without a lot of work. For example, there are easy ways to implement black box testing (from an external package), skip long-running tests (using the -short
and/or -timeout
flags), run benchmarks [func BenchmarkXxx(*testing.B)
], and run tests and benchmarks in parallel [t.Parallel() and b.RunParallel
].
The example below demonstrates benchmarking text templating in parallel. The environment variable GOMAXPROCS
controls the number of parallel goroutines created.
Check your test coverage
How do you know how much of your code has been reached by your tests? You use a coverage tool while you run your tests.
Most coverage tools handle debugging-oriented tasks, such as adding break points to your binary and keeping track of them as they are hit. The go test -cover
command works differently. It rewrites your source code to add GoCover.Count
statements before compilation. For example:
func Size(a int) string {
GoCover.Count[0] = 1
switch {
case a < 0:
GoCover.Count[2] = 1
return cnegative"
…
After the tests run, the tool collects the counters and computes the percentage covered by seeing how many were set. This is far more efficient at runtime (each added line compiles to one mov
instruction) than using break points.
Call C, C++, Fortran, Objective C, and SWIG code—or not
Cgo is the feature that allows Go to call C, C++, Fortran, Objective C, and SWIG code. Whether that's a good idea depends on what you're calling and whether your Go program needs to be portable.
To trigger Cgo, you need only import "C"
somewhere in your Go program, for example:
// #include <stdio.h>
// #include <errno.h>
import "C"
…
The Go code can then refer to types such as C.size_t
, variables such as C.stdout
, or functions such as C.putchar
. The go build
command will detect the import and expand the files it processes to build the Go application, treating .c, .s, and .S files as C source; .cc, .cpp, and .cxx files as C++ source; and .f, .F, .for, and .f90 files as Fortran source.
The temptation to use Cgo is strong when you have a large, working body of code that you'd prefer not to translate to Go. There are other factors to consider, however. Cross-compilation is problematic if you use Cgo: You need to find and maintain cross-compilation C toolchains for all your targets. The Go tools, such as testing, profiling, and coverage analysis, don't know about C code. You'll be introducing "thunks" when your code moves from Go to C and back. Finally, you'll lose the often-touted Go advantage of having a single, static binary.