Go cheatsheet

Condensed summary of Go's syntax, type system and peculiarities

Types

Booleans

  • zero initialized when declared without a value
  • cannot be modified by functions, unless when using a pointer
1
2
var zeroInitializedBool bool // false
var valueBool bool = true

Integers

  • zero initialized when declared without a value
  • cannot be modified by functions, unless when using a pointer
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var zeroInitializedInt int // 0
var valueInt int = 5

// Other integer types have a minimum and maximum value
var min8, max8 int8 = -128, 127
var min16, max16 int16 = -32768, 32767
var min32, max32 int32 = -2147483648, 2147483647
var min64, max64 int64 = -9223372036854775808, 9223372036854775807

var min_u8, max_u8 uint8 = 0, 255
var min_u16, max_u16 uint16 = 0, 65535
var min_u32, max_u32 uint32 = 0, 4294967295
var min_u64, max_u64 uint64 = 0, 18446744073709551615

Decimals (float)

  • zero initialized when declared without a value
  • cannot be modified by functions, unless when using a pointer
1
2
3
4
5
var max_f32 float32 = 3.40282346638528859811704183484516925440e+38
var smallest_f32 float32 =  1.401298464324817070923729583289916131280e-45

var max_f64 float64 = 1.797693134862315708145274237317043567981e+308
var smallest_f64 float64 = 4.940656458412465441765687928682213723651e-324

Runes

  • zero initialized when declared without a value
  • cannot be modified by functions, unless when using a pointer
1
2
3
4
5
6
7
var r1 rune = 'A'

// a line break
var line_break rune = '\n'

// a unicode checkmark symbol ✓
var checkmark rune = '\u2713' 

Pointers

1
2
3
var value int = 5
var pointer *int = &value
println(value == *pointer)

Arrays

  • zero initialized when declared without a value
  • cannot be modified by functions, unless when using a pointer
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var arrayOfZeros [5]int
var array = [5]int{0, 1, 2, 10: 10, 11}
// note the colon : syntax to initialize the element at index 10

nElementsCopied := copy(dst, src)
length := len(array)

firstElement = array[0]
lastElement = array[len(array)-1]
fullSlice = array[:]
subSlice = array[3:5]

Slices

Slices are dynamically allocated, variable-length arrays.

  • They are default initialized to nil.
  • Their values can be modified in function, with caveats: Their length, capacity and memory location cannot be modified in functions because they are passed by value. However, the value of elements can be modidied by functions since they are accessed through the pointer.

Their implementation can be illustrated as follows:

1
2
3
4
5
struct slice {
    length int
    capacity int
    memory *T
}

Slices Cheatsheet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var nilSlice []int  // == nil
var emptySlice = []int{}  // length=0, capacity=0
var sliceWithValues = []int{
    0, 1, 2, 3,
    10: 10,
}
var sliceWithLength = make([]int, length)
var sliceWithLengthAndCapacity = make([]int, length, capacity)

firstElement = slice[0]

length := len(slice)
slice = append(slice, element1, element2)
slice = append(slice, ...otherSlice)
yesno = slices.Equal(slice1, slice2)
yesno = slice1 == nil
nElementsCopied := copy(dst, src)

Maps

Implemented with pointers:

  • They are default initialized to nil,
  • Their values can be modified in function, with caveats.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var nilMap map[string]int // == nil
var emptyMap = map[string]int{}
var mapWithValues = map[string]int{
    "https://takia.dev": 100,
}
var mapWithLength = make(map[string]int, length)

// read
v, ok := emptyMap["hello"]
if !ok {
    // ...
}

delete(mapWithValues, "key")
maps.Equal(map1, map2)

Control Structures

If statements

1
2
3
4
5
6
7
8
a := 5
if a == 5 {
    // ...
} else if a > 5 {
    // ...
} else {
    // ...
}

If statements can use if-local variables as follows:

1
2
3
4
5
6
if n := rand.Intn(10); n < 5 {
    // n exists in this block
} else {
    // n exists in this block too
}
// n does not exist outside the if block

Switch statements

Classic switch:

1
2
3
4
5
6
switch value {
case 0, 1, 2:
    // ...
default:
    // ...
}

Empty switch:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
value := 0

switch {
case value < 5:
    // ...
case value > 5:
    // ...
default:
    // ...
}

Switch-local variables:

1
2
3
4
5
6
7
8
9
switch value := 5; {
case value < 5:
// ...
}

switch value := 5; value {
case 0, 1, 2:
// ...
}

For loops

Classic C-style for loop:

1
2
for i := 0; i < 10; i++ {
}

Condition-only for loop:

1
2
3
4
i := 0
for i < 10 {
    i += 1
}

Sequence iteration:

1
2
3
4
s := []string{"a", "b", "c"}
for index, value := range s {
    println(index, value)
}

Map iteration:

1
2
3
4
m := map[string]string{"key1": "value1"}
for key, value := range m {
    println(key, value)
}

String iteration (as array of rune):

1
2
3
4
5
str := "Hello 😊"
for index, rune := range str {
    // for iterates over runes, not bytes
    println(index, rune)
}

Labeled for loops allow using break or continue on an outer loop from an inner loop:

1
2
3
4
5
6
7
8
// labeled for loop
outer:
for i_outer := range []int{1, 2, 3, 4} {
    for i_inner := range []int{1, 2, 3} {
        unused(i_outer, i_inner)
        continue outer
    }
}

Goto statements

Goto statements can be use to skip over blocks of code. Although it should only be sparingly used, it can sometimes come handy for error handling. Use of defer is preferred when possible.

1
2
3
4
5
6
7
8
jumpOver := true

if jumpOver {
    goto skip
}
println("This statement will be skipped")
skip:
// ...

Functions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

func simpleFunction(arg1 int, arg2 int) int {
    return arg1 + arg2
}

func genericFunction[T any](arg1 T) T {
    return arg1
}

func variadicFunction(vals ...int) int {
    result := 0
    for _, v := range vals {
        result += v
    }
    return result
}

func multipleReturnValues() (int, error) {
    return 0, nil
}

func namedReturnValues() (retA int, err error) {
    // defer registers statements to be executed at exit
    // named return values allows defer to modify them
    defer func() {
        if err != nil {
            retA = 0
            // rollback code
        }
    }()

    return 1, nil
}

Functions can be stored in variables:

1
2
3
4
5
6
// Function variables
var myfunc func(int, int) int = simpleFunction

// Function types
type FuncType func(int, int) int
var myfunc2 FuncType = simpleFunction

Anonymous Function can access outer variables:

1
2
3
4
5
6
7
8
9

func f() {
    outerVariable := 5
    setValue := func(arg int) {
        outerVariable = arg
    }
    setValue(7)
    println(outerVariable)
}

User-Defined types

User-Defined types

User-Defined type can be declared as follows:

1
type NewType ExistingType

There is no implicit cast between user-defined types. The code belows shows how this can be exploited to ensure that every string has been escaped before it is displayed on a webpage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type EscapedString string

func safeEscape(s string) EscapedString {
    escaped := s //html.EscapeString(s)
    return EscapedString(escaped)
}

func displayOnPage(s EscapedString) {
    // ...
}

Structs

Structs are passed by value, they cannot be modified by functions unless when using a pointer. Struct fields are default initialized to their respective zero values.

1
2
3
4
5
6
7
8
9
type person struct {
    name string
    age  int
}

var structOfZeros person // == {"", 0}
var julien = person{"Julien", 30}
var alisha = person{name: "Alisha"}
alisha.age = 30

Methods & Member functions

Methods can be defined on user-defined types as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type person struct {
    name string
    age  int
}

// value-receiver methods cannot modify the instance
func (p person) String() string {
    return fmt.Sprintf("%s (age:%d)", p.name, p.age)
}

// pointer-receiver methods can modify the instance
func (p *person) GrowOld() {
    // handle case where p == nil!
    p.age += 1
}

Go will automatically reference or dereference values and pointer to match the value-receiver method or pointer-receiver method. But remember that value-receiver methods work on a copy of the object and therefore should not attempt to modify its member values.

1
2
3
4
5
6
7
p1 := person{"Julien", 30}
p1.String()
p1.GrowOld() // auto reference

p2 = &p1
p2.String() // auto dereference
p2.GrowOld()

Type Embeddings

Type embeddings embed one InnerType inside an OuterType and make methods defined on InnerType available on instances of OuterType:

For instance, the given Inner type:

1
2
3
4
5
6
7
type Inner struct {
    innerField int
}

func (i Inner) ShowInnerField() {
    println(i.innerField)
}

Can be embedded inside an Outer type as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type Outer struct {
    Inner
    outerField int
}

o := Outer{
    Inner: Inner{
        innerField: 5,
    },
    outerField: 10,
}

And the Inner method can be called on the Outer object:

1
o.ShowInnerField()

Embedding merge the method set of Inner and Outer making it possible to implement interfaces more easily. But beware that:

Embeddings are not inheritance:

  • they do not provide implicit slicing or type cast from derived type to parent type
  • they do not provide dynamic dispatch and method resolution

Interfaces

Interfaces specify what the caller code needs, they define the set of method than an object is expected to offer. Interfaces are similar to python’s Protocols: unlike in Java they are implemented implicitly.

1
2
3
type Provider interface {
    ProviderID() string
}

Any object with a Read() string method automatically implements the Reader interface.

1
2
3
4
5
6
type MyProvider struct {
    name string
}
func (p MyProvider) ProviderID() string {
    return p.name
}

The following function requests a Provider instance and can be used with our MyProvider object implicitely:

1
2
3
4
5
6
func useProvider(p Provider) {
	// ...
}

var p = MyProvider{"name"}
useProvider(p)

Interface Embeddings

Interfaces can be merged together using interface embeddings:

1
2
3
4
5
6
7
type Reader interface{}
type Writer interface{}

type ReadWriter interface {
    Reader
    Writer
}

nil Interfaces

Under the hood, interface are implemented with two pointers: one for the instance and one for the instance type:

1
2
3
4
type Interface[T any] struct {
    type *Type[T]
    value *T
}

An interface is considered nil if it has not been assigned a type. In the code below, even though nilInstance is set to the nil pointer, the nilInterface object is assigned the Object type and therefore does not evaluate to nil:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

type Interface interface {
	Method()
}

type Object struct{}
func (o Object) Method() {
}

var nilInstance *O
var notNilInterface I = nilInstance

println("Instance is nil:", nilInstance == nil) // true
println("Interface is nil:", notNilInterface == nil) // false

A common pitfall is to pre-declare the error variable to return at the end of a function as in this snippet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type CustomError struct {}
func (c CustomError) Error() string { return "message" }

func brokenFunction(arg int) error {
    var err CustomError; // nil CustomError
    if arg < 0 {
        err = CustomError{}
    }
    return err // BUG! The error interface is not nil
}

The function would then be used as follows:

1
2
3
4
var err error = brokenFunction(5)
if err != nil {
    println("ERROR")
}

Which will always enter the error block because the error interface does not evaluate to nil. (Its type pointer is assigned to CustomError!)

To avoid this pitfall, declare variables of the error type instead of the custom error type.

Enums

Go uses iota, a special counter whose value increases for each constant in a const block. It can be used to assign increasing values to consecutive const values. Note that it is possible to use _ to disable the default value.

1
2
3
4
5
6
7
type EnumType int
const (
    EnumDefaultValue EnumType = iota
    EnumValue1
    EnumValue2
    EnumValue3
)

Generics

A function or type is said generic when it accepts type arguments. Type arguments are passed between square brackets []. In the code below, the generic type argument T implements the comparable constraint. Any interface can be used as a type constraint.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
type Set[T comparable] struct {
	storage map[T]bool
}

func (s *Set[T]) add(value T) {
	if s.storage == nil {
		s.storage = map[T]bool{}
	}
	s.storage[value] = true
}
func (s *Set[T]) remove(value T) {
	delete(s.storage, value)
}
func (s Set[T]) contains(value T) bool {
	return s.storage[value]
}

var s = Set[int]{}
s.add(5)
println("Set contains 5?", s.contains(5))
s.remove(5)
© 2020-2025 Takia.Dev
Last updated on Aug 31, 2024 00:00 UTC