Martin Heller
Contributor

Golang tutorial: Get started with the Go language

how-to
Nov 02, 202214 mins
Google GoSoftware Development

Go is a concise, simple, safe, and fast compiled language with outstanding concurrency features. Now, it has generics, too. Isn’t it time you gave Go a try?

cliff diving taking the plunge dive into a project ocean swimming by aydinmutlu getty 2400x1600

Go is an open source programming language from Google 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 includes Occam, Erlang, Newsqueak, and Limbo. The Go language project currently has more than 1,800 contributors and is led by Rob Pike, a distinguished engineer at Google.

Go was originally developed as an alternative to C++. Essentially, Pike got tired of long C++ compilations of a core Google program. As it turned out, though, very few Go language converts came over from C++. When asked what surprised him most after rolling out Go in 2012, Pike responded, “Although we expected C++ programmers to see Go as an alternative, instead most Go programmers come from languages like Python and Ruby.”

At the time, almost everyone coming from C++ or Java wanted Go to have classes and generics. Pike and others pushed back, but in 2022, things changed. As of Go 1.18, generics are finally part of the Go language.

This article demonstrates some of the differentiating features of Go, including extremely lightweight concurrency patterns and the new generic types.

Slices

Go extends the idea of arrays with slices, which have variable size. A slice points to an array of values and includes a length. As an example, [ ]T is a slice with elements of type T. In the following code, we use slices of slices of unsigned bytes to hold the pixels of an image we generate, where the pixel values range from 0 to 255. Go programs start running with package main. The import statement is an extended version of C and C++’s include statement.


package main

import "code.google.com/p/go-tour/pic"

func Pic(dx, dy int) [][]uint8 {
	slice := make([][]uint8, dy)
	for i := range slice {
		slice[i] = make([]uint8, dx)
		for j := range slice[i] {
			slice[i][j] = uint8(i * j)
		}
	}
	return slice
}

func main() {
	pic.Show(Pic)
}

The := syntax declares and initializes a variable, and the compiler infers a type whenever it can. Note, also, that make is used to create slices and some other types. A for...range loop is the equivalent of C#’s for...in loop. The pattern shown in Figure 1 is determined by the expression in the inner loop above: (i*j). See the pic package and its source code to learn more.

go slices IDG

Figure 1. A pattern demonstrating slices in Go.

Maps

The Go map statement maps keys to values. As with slice, you create a map with make, not new. In the following example, we map string keys to integer values. This code demonstrates inserting, updating, deleting, and testing for map elements.


package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
}

Here is the program’s print output:


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, which are called fields. Each field 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 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 ampersands (&) signify pointers, as in C. This program prints 5.


package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := &Vertex{3, 4}
	fmt.Println(v.Abs())
}

Interfaces

An interface type is defined by a set of methods. A value of the interface type can hold any value that implements those methods. In this next example, we define an interface, Abser, and a variable (a) of type Abser:


package main

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f: MyFloat(-math.Sqrt2
	v = Vertex{3, 4}

	a = f // a MyFloat implements Abser 
	a = &v // a *Vertex implements Abser
	// In the following line, v is a Vertex (not *Vertex) 
	// and does NOT implement Abser. 
	a = v

	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 { 
	if f < 0 {
		return float64(-f)
	}
		return float64(f)
	}

type Vertex struct { 
	X, Y float64
}

Note that the assignments a=f and a=&v work, but the assignment a=v does not even compile. The Abs method of Vertex, which you saw in the previous section, has a pointer to the 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. Cases automatically break unless they end with fallthrough statements. The cases are evaluated in the order they are defined.


package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Print("Go runs on ")
	switch os = runtime.GOOS; os {
	case "darwin":
		fmt.Println("macOS.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.", os)
	}
}

Goroutines

Goroutines are basically extremely lightweight threads, in the spirit of Tony Hoare’s Communicating Sequential Processes. In the example below, the first line of func main calls the say function asynchronously, while the second line calls it synchronously. The difference is in the use of the go qualifier for the asynchronous goroutine:


package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

Goroutines, channels, and select statements are the core of Go’s highly scalable concurrency, one of the language’s strongest selling points. Go also has conventional synchronization objects, but they are rarely needed. This program 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. Here’s an example:


package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}

Note that the value of an uninitialized channel is nil. c = make(chan int) creates a bidirectional channel of integers. We could also make unidirectional sending (<-c) and receiving (c<-) channels. Following that, we call sum asynchronously with slices of the first and second half of a. Then, the integer variables, x and y, receive the two sums from the channel. In the expression “for _, v range a“, 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

In this example, we see how 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.


package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

The for loop at the third line of main (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. The cap is set as the optional second argument when you make a channel, as in the first line of main. 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.


package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

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 the third line of main waits to receive values (<-c), then prints them. After 10 values, it sets the quit channel, so the fibonacci function knows to stop.

Concurrency patterns in Go

We’ve looked at a few differentiating features of the Go language. Now, let’s see how they work together in programming examples. We’ll start with a couple of concurrency patterns in Go, both taken from Rob Pike’s 2012 talk on Concurrency Patterns in Go.

Concurrency pattern #1: fan in

In this example, we’re using 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 are using the same temporary variable name to hold the string from their respective input channels.


package main

func fanIn(input1, input2 <-chan string) <-chan string {
	c := make(chan string)
	go func() {
	    for {
		 select {
		 case s := <-input1: <- s 				
		 case s := <-input2: c <- s
			}
		}
	}()
	return c
}

This concurrency example implements a parallel search of the internet, sort of like what the Google search engine actually does. To begin with, replicas …Search is a variadic parameter to the function; both Search and Result are types defined elsewhere:


package main

func First(query string, replicas ...Search) Result {
	c := make(chan Result)
	searchReplica := func(i int) { c <- replicas[i](query) }
	for i := range replicas {
		go searchReplica(i)
	}
	return <-c
}

The caller passes a given number of (N) search server functions to the First function. The First function creates a channel, c, for results and defines a function to query the ith server, then saves it in searchReplica. First then calls searchReplica asynchronously for all N servers, always returning the answer on channel c. It returns the first result to come back from the servers.

Packages in Go

Next, we’ll look at a couple of packages.

The 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:


package main

import (
	"log"
	"net/http"
)

func main() {
	// Simple static webserver:
	log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}

This example doesn’t work properly in the Go Playground online environment. If you run it on a Mac command line, however, it returns the following to a web browser asking for https://localhost:8080/:

bash/ ccid/ cups/ groff/ ntp/ postfix/

The template package

The html/template package implements data-driven templates for generating HTML output that is safe against code injection. For example:


package main

import "html/template"

func main() {
	t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
	err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
}

The code above produces safe, escaped HTML output:


Hello, &lt;script&gt; alert(&#39; you have been pwned&#39;)&lt;/script&gt;!

Without the escaping added by the html/template package in this example, we could have produced the following runnable JavaScript string:


Hello, <script> alert(' you have been pwned')</script>!

Generics in Go

Yes, at long last, Go has generics. They are deeply integrated into the language, and include the idea of type constraints. This means a function’s type parameters appear between brackets, before the function’s arguments. In general, a function implemented with generic types is more efficient than a function implemented with the any type, and is less wasteful than reimplementing the function for every type that may be of interest. Seeing a group of functions in your code that differ only in their argument types should be a red flag to consider writing a generic version, instead.

The following two examples are from a new section of the Tour of Go programming page.

Generics example #1: Index function for comparable types

In the following code, func Index[T comparable](s []T, x T) has a type parameter of T, and says that s is a slice of any type, T, that fulfills the built-in constraint comparable. Note that x is also a value of the same type.


package main

import "fmt"

// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
	for i, v := range s {
		// v and x are type T, which has the comparable
		// constraint, so we can use == here.
		if v == x {
			return i
		}
	}
	return -1
}

func main() {
	// Index works on a slice of ints
	si := []int{10, 20, 15, -10}
	fmt.Println(Index(si, 15))

	// Index also works on a slice of strings
	ss := []string{"foo", "bar", "baz"}
	fmt.Println(Index(ss, "hello"))
}

Because both integers and strings are comparable, the Index function accepts either one. This example prints 2, then -1, which are correct answers. Note that Go, like C and C++, starts counting from 0.

Generics example #2: Generic types

This example from the Go tour “demonstrates a simple type declaration for a singly-linked list holding any type of value.” As an exercise, you are invited to add functionality to the following list implementation:


package main

// List represents a singly-linked list that holds
// values of any type.
type List[T any] struct {
	next *List[T]
	val  T
}

func main() {
}

How much or how little functionality you add is up to you, but I suggest at least implementing Next() for elements and New(), Front(), Back(), and InsertAfter() for lists. An Add() method that essentially performs Back() and InsertAfter() would also be good, as would a Length() method, an Index() method, a Remove() method, and perhaps an InsertBefore() method.

Usually, singly-linked list implementations don’t maintain a list length number, unlike doubly-linked list implementations, so you’ll need to traverse the list from front to back to count the elements to implement Length(). You’ll also have to traverse the list to find the back and to search for a value for the Index() method.

Learn more about programming with Go

Follow these links to go further with Go:

Martin Heller
Contributor

Martin Heller is a contributing editor and reviewer for InfoWorld. Formerly a web and Windows programming consultant, he developed databases, software, and websites from his office in Andover, Massachusetts, from 1986 to 2010. More recently, he has served as VP of technology and education at Alpha Software and chairman and CEO at Tubifi.

More from this author