Learn Go in ~5mins
This is inspired by A half-hour to learn Rust
and Zig in 30 minutes.
Basics
Your first Go program as a classical “Hello World” is pretty simple:
First we create a workspace for our project:
1mkdir hello
Next we create and initialize a Go module:
1go mod init hello
Then we write some code using our favorite editor:
1
2package main
3
4import "fmt"
5
6func main() {
7 fmt.Println("Hello World!")
8}
And finally we build and produce a binary:
1go build
You should now have a hello
binary in your workspace, if you run it you should also get the output:
1$ ./hello
2Hello World!
Variables
You can create variables in Go in one of two ways:
1var x int
Other types include int
, int32
, int64
, float32
, float64
, bool
and string
(and a few others…), there are also unsigned variants of the
integer types prefixed with u
, e.g: uint8
which is the same as a byte
.
Or implicitly with inferred types by creating and assigning a value with:
1x := 42
Values are assigned by using the =
operator:
1x = 1
Functions
Functions are declared with the func
keyword:
1func hello(name string) string {
2 return fmt.Sprintf("Hello %s", name)
3}
Functions with a return type must explicitly return a value.
Functions can return more than one value (commonly used to return errors and values):
1func isEven(n int) (bool, error) {
2 if n <= 0 {
3 return false, fmt.Errorf("error: n must be > 0")
4 }
5 return n % 2 == 0, nil
6}
Go also supports functions as first-class citizens and as such supports many aspects of functional programming, including closures, returning functions and passing functions around as values. For example:
1func AddN(n int) func(x int) int {
2 return func(x int) int {
3 return x + n
4 }
5}
Structs
As Go is a multi-paradigm language, it also support “object orientated”
programming by way of “structs” (borrowed from C). Objects / Structs are
defined with the struct
keyword:
1type Account struct {
2 Id: int
3 Balance: float64
4}
Fields are defined similar to variables but with a colon :
separating their
name and type. Fields are accessed with the dot-operator .
:
1account := Account{}
2fmt.Println("Balance: $%0.2f", account.Balance)
Methods
Structs (objects) can also have methods. Unlike other languages however Go does not support multiple-inheritance nor does it have classes (you can however embed structs into other structs).
Methods are created like functions but take a “receiver” as the first argument:
1type Account struct {
2 id int
3 bal float64
4}
5
6func (a *Account) String() string {
7 return fmt.Sprintf("Account[%d]: $0.2f", a.id, a.bal)
8}
9
10func (a *Account) Dsposit(amt flaot64) float64 {
11 a.bal += amt
12 return a.bal
13}
14
15func (a *Account) Withdraw(amt float64) float64 {
16 a.bal -= amt
17 return a.bal
18}
19
20func (a *Account) Balance() float64 {
21 return a.bal
22}
These are called “pointer receiver” methods because the first argument is a
pointer to a struct of type Account
denoted by a *Account
.
You can also define methods on a struct like this:
1type Circle struct {
2 Radius float64
3}
4
5func (c Circle) Area() float64 {
6 return 3.14 * c.Radius * c.Radius
7}
In this case methods cannot modify any part of the struct Circle
,
they can only read it’s fields. They are effectively “immutable”.
Arrays and Slices
Arrays are created with [T]
like this:
1var xs []int = []int{1, 2, 3, 4}
Arrays can also be created and appended to:
1var xs []int
2xs = append(xs, 1)
3xs = append(xs, 2)
4xs = append(xs, 3)
You can access an array’s elements by indexing:
1xs[1] // 2
You can also access a subset of an array by “slicing” it:
1ys := xs[1:] // [2, 3]
You can iterate over an array/slice by using the range
keyword:
1for i, x := range xs {
2 fmt.Printf("xs[%d] = %d\n", i, x)
3}
Maps
Go has a builtin data structure for storing key/value pairs called maps (called hash table, hash map, dictionary or associative array in other languages).
You create a map by using the keyword map
and defining a type for keys
and type for values map[Tk]Tv
, for example a map with keys as strings
and values as integers can be defined as:
1var counts map[string]int
You can assign values to a map just like arrays by using curly braces {...}
where keys and values are separated by a colon :
, for example:
1var counts = map[string]int{
2 "Apples": 4,
3 "Oranges": 7,
4}
Maps can be indexed by their keys just like arrays/slices:
1counts["Apples"] // 4
And iterated over similar to array/slices:
1for key, value := range counts {
2 fmt.Printf("%s: %d\n", key, value)
3}
The only important thing to note about maps in Go is you must initialize
a map before using it, a nil
map will cause a program error and panic:
1var counts map[string]int
2counts["Apples"] = 7 // This will cause an error and panic!
You must initialize a map before use by using the make()
function:
1var counts map[string]int
2counts = make(map[string]int)
3counts["Apples"] = 7
Flow control structures
Go only has one looping construct as seen in the previous sections:
1sum := 0
2for i := 0; i < 10; i++ {
3 sum += i
4}
The basic for
loop has three components separated by semicolons:
- the init statement: executed before the first iteration
- the condition expression: evaluated before every iteration
- the post statement: executed at the end of every iteration
If you omit the condition you effectively have an infinite loop:
1for {
2}
3// This line is never reached!
Go has the usual if
statement along with else if
and else
for branching:
1var N = 42
2
3func Guess(n int) string {
4 if n == 42 {
5 return "You got it!"
6 } else if n < N {
7 return "Too low! Try again..."
8 } else {
9 return "Too high! Try again..."
10 }
11}
Note: The last else
could have been omitted and been written as
return "Too high~ Try again..."
, as it would have been
functionally equivalent.
There is also a switch
statement that can be used in place of multiple if
and else if
statements, for example:
1func FizzBuzz(n int) string {
2 switch n {
3 case n % 15 == 0:
4 return "FizzBuzz"
5 case n % 3 == 0:
6 return "Fizz"
7 case n % 5 == 0:
8 return "Buzz"
9 default:
10 return fmt.Sprintf("%d", n)
11 }
12}
Functions can be executed at the end of a function anywhere in your function
by “deferring” their execution by using the defer
keyword. This is commonly
used to close resources automatically at the end of a function, for example:
1package main
2
3import (
4 "os"
5 "fmt"
6)
7
8func Goodbye(name string) {
9 fmt.Printf("Goodbye %s", name)
10}
11
12func Hello(name string) {
13 defer Goodbye(name)
14 fmt.Printf("Hello %s", name)
15}
16
17func main() {
18 user := os.Getenv("User")
19 Hello(user)
20}
This will output when run:
1$ ./hello
2Hello prologic
3Goodbye prologic
Error handling
Errors are values in Go and you return them from functions. For example
opening a file with os.Open
returns a pointer to the open file and nil
error on success, otherwise a nil
pointer and the error that occurred:
1f, err := os.Open("/path/to/file")
You check for errors like any other value:
1f, err := os.Open("/path/to/file")
2if err == nil {
3 // do something with f
4}
It is idiomatic Go to check for non-nil
errors from functions and return
early, for example:
1func AppendFile(fn, text string) error {
2 f, err := os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.WR_ONLY, 0644)
3 if err != nil {
4 return fmt.Errorf("error opening file for writing: %w", err)
5 }
6 defer f.Close()
7
8 if _, err := f.Write([]byte(text)); err != nil {
9 return fmt.Errorf("error writing text to fiel: %w", err)
10 }
11
12 return nil
13}
Creating and import packages
Finally Go (like every other decent languages) has a module system where you
can create packages and import them. We saw earlier In Basics how we
create a module with go mod init
when starting a new project.
Go packages are just a directory containing Go source code.
The only difference is the top-line of each module (each *.go
source file):
Create a Go package by first creating a directory for it:
1mkdir shapes
And initializing it with go mod init
:
1cd shapes
2go mod init github.com/prologic/shapes
Now let’s create a source module called circle.go
using our favorite editor:
1package shapes
2
3type Circle struct {
4 Radius float64
5}
6
7func (c Circle) String() string {
8 return fmt.Sprintf("Circle(%0.2f)", c.Radius)
9}
10
11func (c Circle) Area() float64 {
12 return 3.14 * c.Radius * c.Radius
13}
It is important to note that in order to “export” functions, structs or package scoped variables or constants, they must be capitalized or the Go compiler will not export those symbols and you will not be able access them from importing the package.
Now create a Git repository on Github called “shapes” and push your package to it:
1git init
2git commit -a -m "Initial Commit"
3git remote add origin git@github.com:prologic/shapes.git
4git push -u origin master
You can import the new package shapes
by using it’s fully qualified “importpath”
as github.com/prologic/shapes
. Go automatically knows hot to fetch and build
the package given its import path.
Example:
Let’s create a simple program using the package github.com/prologic/shapes
:
1mkdir hello
2go mod init hello
And let’s write the code for main.go
using our favorite editor:
1
2package main
3
4import (
5 "fmt"
6
7 "github.com/prologic/shapes"
8)
9
10func main() {
11 c := shapes.Circle{Radius: 5}
12 fmt.Printf("Area of %s: %0.2f\n", c, c.Area())
13}
Building it with go build
:
1go build
And finally let’s test it out by running the resulting binary:
1$ ./hello
2Area of Circle(5.00): 78.50
Congratulations! 🎉
Now you’re a Gopher!
That’s it! Now you know a fairly decent chunk of Go. Some (pretty important) things I didn’t cover include:
- Writing unit tests, writing tests in Go is really easy! See testing
- The standard library, Go has a huge amount of useful packages in the standard library. See Standard Library.
- Goroutines and Channels, Go’s builtin concurrency is really powerful and easy to use. See Concurrency.
- Cross-Compilation, compiling your program for other architectures
and operating systems is super easy. Just set the
GOOS
andGOARCH
environment variables when building.
For more details, check the latest documentation, or for a less half-baked tutorial, please read the official Go Tutorial and A Tour of Go.