Getting started

Introduction

Hello world

package main

import "fmt"

func main() {
  message := greetMe("world")
  fmt.Println(message)
}

func greetMe(name string) string {
  return "Hello, " + name + "!"
}
$ go mod init hello
$ go run .

Or try it in the Go playground.

Commands

CommandDescription
go run .Build and run
go buildCompile package
go test ./...Run tests
go vet ./...Report likely bugs
go fmt ./...Format packages
go mod tidySync dependencies

See: go command

Variables

Variable declaration

var msg string
var msg = "Hello, world!"
var msg string = "Hello, world!"
var x, y int
var x, y int = 1, 2
var x, msg = 1, "Hello, world!"

Declaration list

var (
  x int
  y = 20
  z int = 30
  d, e = 40, "Hello"
  f, g string
)

Shortcut of above (infers type)

msg := "Hello"
x, msg := 1, "Hello"

Short declarations infer the type and only work inside functions.

See: Variables

Constants

const Phi = 1.618
const Size int64 = 1024
const x, y = 1, 2
const (
  Pi = 3.14
  E  = 2.718
)
const (
  Sunday = iota
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
)

Constants can be character, string, boolean, or numeric values.

See: Constants

Basic types

Strings

str := "Hello"
str := `Multiline
string`

Strings are of type string.

See: String types

Numbers

Typical types

num := 3          // int
num := 3.0        // float64
num := 3 + 4i     // complex128
num := byte('a')  // uint8 alias
num := 'a'        // rune (int32)

Other types

var u uint = 7        // unsigned
var p float32 = 22.7  // 32-bit float

Built-ins

lo := min(3, 9)   // 3
hi := max(3, 9)   // 9

byte and rune are aliases for uint8 and int32.

See: Numeric types

Arrays

// var numbers [5]int
numbers := [...]int{0, 0, 0, 0, 0}

Arrays have a fixed size.

See: Arrays

Slices

slice := []int{2, 3, 4}
slice := []byte("Hello")
s := make([]int, 3, 5)  // len 3, cap 5
s = append(s, 4)

Slices have a dynamic size, unlike arrays.

s := []int{3, 1, 2}
slices.Sort(s)        // [1 2 3]
slices.Contains(s, 2) // true

The slices package has generic helpers for sorting and searching.

See: Slices, slices package

Maps

m := map[string]int{"a": 1}
m["b"] = 2
delete(m, "a")
v, ok := m["b"]  // comma-ok
clear(m)

A missing key returns the zero value; ok reports whether it exists.

See: Maps, maps package

Pointers

func main() {
  b := *getPointer()
  fmt.Println("Value is", b)
}
func getPointer() (p *int) {
  a := 234
  return &a
}
p := new(int)
*p = 234

Pointers point to a memory location of a variable. Go is fully garbage-collected and has no pointer arithmetic.

q := new(42)  // pointer to 42

Since Go 1.26, new accepts an expression as the initial value.

See: Pointers

Type conversions

i := 2
f := float64(i)
u := uint(i)

See: Type conversions

Flow control

Conditional

if day == "sunday" || day == "saturday" {
  rest()
} else if day == "monday" && isTired() {
  groan()
} else {
  work()
}

See: If

Statements in if

if _, err := doThing(); err != nil {
  fmt.Println("Uh oh")
}

An if can run a statement before the condition. Its variables are scoped to the if.

See: If with a short statement

Switch

switch day {
case "sunday":
  // cases don't "fall through" by default!
  fallthrough
case "saturday":
  rest()
default:
  work()
}

See: Switch

For loop

for count := 0; count <= 10; count++ {
  fmt.Println("My counter is at", count)
}
for i := range 3 {
  fmt.Println(i)  // 0 1 2
}

Since Go 1.22 the range clause also accepts an integer.

See: For statements

For-range loop

entry := []string{"Jack", "John"}
for i, val := range entry {
  fmt.Printf("%d: %s\n", i, val)
}

Since Go 1.22 each iteration gets its own loop variables.

See: For statements, Range

While loop

n := 0
for n < 42 {
  n += guess()
}

That's Go's only loop: for with just a condition.

See: Go's "while"

Functions

Lambdas

myfunc := func() bool {
  return x > 10000
}

Functions are first-class objects.

See: Function literals

Multiple return types

a, b := getMessage()
func getMessage() (a, b string) {
  return "Hello", "World"
}

See: Multiple results

Named return values

func split(sum int) (x, y int) {
  x = sum * 4 / 9
  y = sum - x
  return
}

By defining the return value names in the signature, a return (no args) will return variables with those names.

See: Named return values

Variadic functions

func sum(nums ...int) int {
  total := 0
  for _, n := range nums {
    total += n
  }
  return total
}
sum(1, 2, 3)

See: Variadic functions

Generics

Type parameters

func Map[T, U any](xs []T, f func(T) U) []U {
  out := make([]U, 0, len(xs))
  for _, x := range xs {
    out = append(out, f(x))
  }
  return out
}
type Stack[T any] struct {
  items []T
}
func (s *Stack[T]) Map[U any](f func(T) U) []U {
  out := make([]U, 0, len(s.items))
  for _, v := range s.items {
    out = append(out, f(v))
  }
  return out
}

Go 1.27 allows methods to declare their own type parameters.

See: Type parameters, Generic methods

Constraints

type Number interface {
  ~int | ~int64 | ~float64
}

func Sum[T Number](xs []T) T {
  var total T
  for _, x := range xs {
    total += x
  }
  return total
}

~T accepts any type whose underlying type is T.

See: Type constraints

Packages

Importing

import "fmt"
import "math/rand"
import (
  "fmt"        // gives fmt.Println
  "math/rand"  // gives rand.Intn
)

Both are the same.

See: Importing

Aliases

import r "math/rand"
import _ "embed"  // side effects
r.Intn()

A _ alias imports a package only for its side effects.

See: Import declarations

Exporting names

func Hello() {
  // ...
}

Exported names begin with capital letters.

See: Exported names

Packages

package hello

Every package file has to start with package.

See: Packages

Concurrency

Goroutines

func main() {
  // A "channel"
  ch := make(chan string)

  // Start concurrent routines
  go push("Moe", ch)
  go push("Larry", ch)
  go push("Curly", ch)

  // Read 3 results
  // (Since our goroutines are concurrent,
  // the order isn't guaranteed!)
  fmt.Println(<-ch, <-ch, <-ch)
}
func push(name string, ch chan string) {
  msg := "Hey, " + name
  ch <- msg
}

Channels are concurrency-safe communication objects, used in goroutines.

See: Goroutines, Channels

Buffered channels

ch := make(chan int, 2)
ch <- 1
ch <- 2
ch <- 3
// fatal error:
// all goroutines are asleep - deadlock!

Buffered channels limit the number of messages they can hold.

See: Buffered channels

Select

select {
case msg := <-ch:
  fmt.Println(msg)
case <-time.After(time.Second):
  fmt.Println("timeout")
}

select waits on multiple channel operations and runs the first ready one.

See: Select

Closing channels

Closes a channel

ch <- 1
ch <- 2
ch <- 3
close(ch)

Iterates across a channel until closed

for i := range ch {
  // ...
}

Closed if ok == false

v, ok := <-ch

See: Range and close

WaitGroup

import "sync"

func main() {
  var wg sync.WaitGroup

  for _, item := range items {
    wg.Go(func() {
      doOperation(item)
    })
  }
  wg.Wait()
}

wg.Go (Go 1.25+) adds and runs a task; Add and Done still work.

See: WaitGroup

Mutex

var mu sync.Mutex
var count int

func inc() {
  mu.Lock()
  defer mu.Unlock()
  count++
}

A sync.Mutex serialises access to shared state.

See: Mutex

Error control

Errors

err := errors.New("not found")
err = fmt.Errorf("load %q: %w", name, err)
if errors.Is(err, os.ErrNotExist) {
  // ...
}
var pathErr *fs.PathError
if errors.As(err, &pathErr) {
  fmt.Println(pathErr.Path)
}
err = errors.Join(err1, err2)

%w wraps an error so errors.Is and errors.As can inspect it.

See: errors package

Defer

func main() {
  defer fmt.Println("Done")
  fmt.Println("Working...")
}

defer runs a function when the surrounding function returns. Arguments are evaluated immediately, but the call runs later.

See: Defer, panic and recover

Deferring functions

func main() {
  defer func() {
    fmt.Println("Done")
  }()
  fmt.Println("Working...")
}

Lambdas are better suited for defer blocks.

func main() {
  var d = int64(0)
  defer func(d *int64) {
    fmt.Printf("& %v Unix Sec\n", *d)
  }(&d)
  fmt.Print("Done ")
  d = time.Now().Unix()
}

Arguments are evaluated when defer runs; a pointer argument still sees the final value.

See: Defer, panic and recover

Panic and recover

defer func() {
  if r := recover(); r != nil {
    fmt.Println("recovered:", r)
  }
}()

panic("boom")

recover stops a panic and returns its value, but only inside a deferred function.

See: Defer, panic and recover

Structs

Defining

type Vertex struct {
  X int
  Y int
}
func main() {
  v := Vertex{1, 2}
  v.X = 4
  fmt.Println(v.X, v.Y)
}

See: Structs

Literals

v := Vertex{X: 1, Y: 2}
// Field names can be omitted
v := Vertex{1, 2}
// Y is implicit
v := Vertex{X: 1}

You can also specify field names.

See: Struct literals

Pointers to structs

v := &Vertex{1, 2}
v.X = 2

Doing v.X is the same as doing (*v).X, when v is a pointer.

See: Pointers to structs

Embedding

type Base struct {
  ID int
}

type User struct {
  Base
  Name string
}

u := User{
  Base: Base{ID: 7},
  Name: "Ada",
}
fmt.Println(u.ID)  // promoted field

Embedding promotes the fields and methods of the embedded type.

See: Embedding

Methods

Receivers

type Vertex struct {
  X, Y float64
}
func (v Vertex) Abs() float64 {
  return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
v := Vertex{1, 2}
v.Abs()

There are no classes, but you can define functions with receivers.

See: Methods

Mutation

func (v *Vertex) Scale(f float64) {
  v.X = v.X * f
  v.Y = v.Y * f
}
v := Vertex{6, 12}
v.Scale(0.5)
// `v` is updated

By defining your receiver as a pointer (*Vertex), you can do mutations.

See: Pointer receivers

Interfaces

A basic interface

type Shape interface {
  Area() float64
  Perimeter() float64
}

See: Interfaces

Implicit implementation

type Rectangle struct {
  Length, Width float64
}

Struct Rectangle implicitly implements interface Shape by implementing all of its methods.

See: Interfaces are implemented implicitly

Methods

func (r Rectangle) Area() float64 {
  return r.Length * r.Width
}

func (r Rectangle) Perimeter() float64 {
  return 2 * (r.Length + r.Width)
}

The methods defined in Shape are implemented in Rectangle.

See: Method sets

Interface example

func main() {
  var r Shape = Rectangle{Length: 3, Width: 4}
  fmt.Printf("%T %v %v\n", r, r.Area(), r.Perimeter())
}

See: Interface values

Type assertions

var v any = "hello"  // any = interface{}

s, ok := v.(string)  // comma-ok
fmt.Println(s, ok)
switch x := v.(type) {
case string:
  fmt.Println("string", x)
case int:
  fmt.Println("int", x)
}

any is an alias for interface{}, added in Go 1.18.

See: Type assertions, Type switches

References

Official resources

0 Comments for this cheatsheet. Write yours!