Getting started
Introduction
- A tour of Go (go.dev)
- Go playground (go.dev)
- Go wiki (go.dev)
Hello world
hello.go
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
| Command | Description |
|---|---|
go run . | Build and run |
go build | Compile package |
go test ./... | Run tests |
go vet ./... | Report likely bugs |
go fmt ./... | Format packages |
go mod tidy | Sync 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
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
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.
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.
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
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.
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.
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.
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.
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
- A tour of Go (go.dev)
- Go wiki (go.dev)
- Effective Go (go.dev)
Other links
- Go by Example (gobyexample.com)
- Awesome Go (awesome-go.com)
- JustForFunc YouTube (youtube.com)
- Style Guide (go.dev)
0 Comments for this cheatsheet. Write yours!