Introductory Go Programming Tutorial

How to get started with this useful new programming language. By Jay Ts

Go Programming

You've probably heard of Go. Like any new programming language, it took a while to mature and stabilize to the point where it became useful for production applications. Nowadays, Go is a well established language that is used in web development, writing DevOps tools, network programming and databases. It was used to write Docker, Kubernetes, Terraform and Ethereum. Go is accelerating in popularity, with adoption increasing by 76% in 2017, and there now are Go user groups and Go conferences. Whether you want to add to your professional skills or are just interested in learning a new programming language, you should check it out.

Go History

A team of three programmers at Google created Go: Robert Griesemer, Rob Pike and Ken Thompson. The team decided to create Go because they were frustrated with C++ and Java, which through the years have become cumbersome and clumsy to work with. They wanted to bring enjoyment and productivity back to programming.

The three have impressive accomplishments. Griesemer worked on Google's ultra-fast V8 JavaScript engine used in the Chrome web browser, Node.js JavaScript runtime environment and elsewhere. Pike and Thompson were part of the original Bell Labs team that created UNIX, the C language and UNIX utilities, which led to the development of the GNU utilities and Linux. Thompson wrote the very first version of UNIX and created the B programming language, upon which C was based. Later, Thompson and Pike worked on the Plan 9 operating system team, and they also worked together to define the UTF-8 character encoding.

Why Go?

Go has the safety of static typing and garbage collection along with the speed of a compiled language. With other languages, "compiled" and "garbage collection" are associated with waiting around for the compiler to finish and then getting programs that run slowly. But Go has a lightning-fast compiler that makes compile times barely noticeable and a modern, ultra-efficient garbage collector. You get fast compile times along with fast programs. Go has concise syntax and grammar with few keywords, giving Go the simplicity and fun of dynamically typed interpreted languages like Python, Ruby and JavaScript.

The idea of Go's design is to have the best parts of many languages. At first, Go looks a lot like a hybrid of C and Pascal (both of which are successors to Algol 60), but looking closer, you will find ideas taken from many other languages as well.

Go is designed to be a simple compiled language that is easy to use, while allowing concisely written programs that run efficiently. Go lacks extraneous features, so it's easy to program fluently, without needing to refer to language documentation while programming. Programming in Go is fast, fun and productive.

Let's Go

First, let's make sure you have Go installed. You probably can use your distribution's package management system. To find the Go package, try looking for "golang", which is a synonym for Go. If you can't install it that way, or if you want a more recent version, get a tarball from https://golang.org/dl and follow the directions on that page to install it.

When you have Go installed, try this command:


$ go version
go version go1.10 linux/amd64

The output shows that I have Go version 1.10 installed on my 64-bit Linux machine.

Hopefully, by now you've become interested and want to see what a complete Go program looks like. Here's a very simple program in Go that prints "hello, world":


package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}

The line package main defines the package that this file is part of. Naming main as the name of the package and the function tells Go that this is where the program's execution should start. You need to define a main package and main function even when there is only one package with one function in the entire program.

At the top level, Go source code is organized into packages. Every source file is part of a package. Importing packages and exporting functions are child's play.

The next line, import "fmt" imports the fmt package. It is part of the Go standard library and contains the Printf() function. Often you'll need to import more than one package. To import the fmt, os and strings packages, you can type either this:


import "fmt"
import "os"
import "strings"

or this:


import (
    "fmt"
    "os"
    "strings"
    )

Using parentheses, import is applied to everything listed inside the parentheses, which saves some typing. You'll see parentheses used like this again elsewhere in Go, and Go has other kinds of typing shortcuts too.

Packages can export constants, types, variables and functions. To export something, just capitalize the name of the constant, type, variable or function you want to export. It's that simple.

Notice that there are no semicolons in the "hello, world" program. Semicolons at the ends of lines are optional. Although this is convenient, it leads to something to be careful about when you are first learning Go. This part of Go's syntax is implemented using a method taken from the BCPL language. The compiler uses a simple set of rules to "guess" when there should be a semicolon at the end of the line, and it inserts one automatically. In this case, if the right parenthesis in main() were at the end of the line, it would trigger the rule, so it's necessary to place the open curly bracket after main() on the same line.

This formatting is a common practice that's allowed in other languages, but in Go, it's required. If you put the open curly bracket on the next line, you'll get an error message.

Go is unusual in that it either requires or favors a specific style of whitespace formatting. Rather than allowing all sorts of formatting styles, the language comes with a single formatting style as part of its design. The programmer has a lot of freedom to violate it, but only up to a point. This is either a straitjacket or godsend, depending on your preferences! Free-form formatting, allowed by many other languages, can lead to a mini Tower of Babel, making code difficult to read by other programmers. Go avoids that by making a single formatting style the preferred one. Since it's fairly easy to adopt a standard formatting style and get used to using it habitually, that's all you have to do to be writing universally readable code. Fair enough? Go even comes with a tool for reformatting your code to make it fit the standard:


$ go fmt hello.go

Just two caveats: your code must be free of syntax errors for it to work, so it won't fix the kind of problem I just described. Also, it overwrites the original file, so if you want to keep the original, make a backup before running go fmt.

The main() function has just one line of code to print the message. In this example, the Printf() function from the fmt package was used to make it similar to writing a "hello, world" program in C. If you prefer, you can also use this:


fmt.Println("hello, world")

to save typing the \n newline character at the end of the string.

Now let's compile and run the program. First, copy the "hello, world" source code to a file named hello.go. Then compile it using this command:


$ go build hello.go

And to run it, use the resulting executable, named hello, as a command:


$ hello
hello, world

As a shortcut, you can do both steps in just one command:


$ go run hello.go
hello, world

That will compile and run the program without creating an executable file. It's great for when you are actively developing a project and are just checking for errors before doing more edits.

Next, let's look at a few of Go's main features.

Concurrency

Go's built-in support for concurrency, in the form of goroutines, is one of the language's best features. A goroutine is like a process or thread, but it's much more lightweight. It's normal for a Go program to have thousands of active goroutines. Starting up a goroutine is as simple as:


go f()

The function f() then will run concurrently with the main program and other goroutines. Go has a means of allowing the concurrent pieces of the program to synchronize and communicate using channels. A channel is somewhat like a UNIX pipe; it can be written to at one end and read from at the other. A common use of channels is for goroutines to indicate when they have finished.

The goroutines and their resources are managed automatically by the Go runtime system. With Go's concurrency support, it's easy to get all of the cores and threads of a multicore CPU working efficiently.

Types, Methods and Interfaces

You might wonder why types and methods are together in the same heading. It's because Go has a simplified object-oriented programming model that works along with its expressive, lightweight type system. It completely avoids classes and type hierarchies, so it's possible to do complicated things with datatypes without creating a mess. In Go, methods are attached to user-defined types, not to classes, objects or other data structures. Here's a simple example:


// make a new type MyInt that is an integer

type MyInt int

// attach a method to MyInt to square a number

func (n MyInt) sqr() MyInt {
    return n*n
}

// make a new MyInt-type variable
// called "number" and set it to 5

var number MyInt = 5

// and now the sqr() method can be used

var square = number.sqr()

// the value of square is now 25

Along with this, Go has a facility called interfaces that allows mixing of types. Operations can be performed on mixed types as long as each has the method or methods attached to it, specified in the definition of the interface, that are needed for the operations.

Suppose you've created types called cat, dog and bird, and each has a method called age() that returns the age of the animal. If you want to add the ages of all animals in one operation, you can define an interface like this:


type animal interface {
    age() int
}

The animal interface then can be used like a type, allowing the cat, dog and bird types all to be handled collectively when calculating ages.

Unicode Support

Considering that Ken Thompson and Rob Pike defined the Unicode UTF-8 encoding that is now dominant worldwide, it may not surprise you that Go has good support for UTF-8. If you've never used Unicode and don't want to bother with it, don't worry; UTF-8 is a superset of ASCII. That means you can continue programming in ASCII and ignore Go's Unicode support, and everything will work nicely.

In reality, all source code is treated as UTF-8 by the Go compiler and tools. If your system is properly configured to allow you to enter and display UTF-8 characters, you can use them in Go source filenames, command-line arguments and in Go source code for literal strings and names of variables, functions, types and constants.

In Figure 1, you can see a "hello, world" program in Portuguese, as it might be written by a Brazilian programmer.

Hello World

Figure 1. Go "Hello, World" Program in Portuguese

In addition to supporting Unicode in these ways, Go has three packages in its standard library for handling more complicated issues involving Unicode.

By now, maybe you understand why Go programmers are enthusiastic about the language. It's not just that Go has so many good features, but that they are all included in one language that was designed to avoid over-complication. It's a really good example of the whole being greater than the sum of its parts.

Resources

About the Author

Jay Ts is a software developer, Linux system administrator and electronic designer. He got started with UNIX and the C Programming Language in 1981, and switched from UNIX to Linux in 1996. He is familiar with many operating systems, Linux distributions and programming languages. Jay formerly worked for Caltech, NASA/JPL and the Information Sciences Institute. He currently lives in Sedona, Arizona. Send comments to jay@jayts.com.