Booleans

var ok bool = true
var done = false

Operators: &&, ||, !. Short-circuit evaluation. There is no implicit conversion between bool and integers — if 1 { ... } is a compile error.

Numeric types

Family Types Notes
Signed int8 int16 int32 int64 Two's complement.
Unsigned uint8 uint16 uint32 uint64 uintptr uint8 is also byte.
Platform int, uint 32 or 64 bits depending on platform.
Float float32 float64 IEEE 754. Default literal: float64.
Complex complex64 complex128 Rarely used outside numerical code.
Aliases byte = uint8, rune = int32 rune represents a Unicode code point.

Conversions are always explicit:

var i int = 65
var f float64 = float64(i)
var u uint  = uint(f)
var c rune  = rune(i)        // 'A'

Mixing types in arithmetic without conversion is a compile error.

Overflow

Signed integer overflow wraps (defined behaviour). Floating point follows IEEE 754. Use math/big for arbitrary precision.

Strings

A string is an immutable read-only slice of bytes. By convention it holds UTF-8 text but can hold any bytes.

s := "héllo"          // UTF-8: 6 bytes
fmt.Println(len(s))   // 6, not 5

for i, r := range s { // i is the byte index, r is the rune
    fmt.Printf("%d: %c\n", i, r)
}

bs := []byte(s)       // copy to mutable byte slice
rs := []rune(s)       // decode to rune slice (length 5)

Concatenation with + is fine for a few strings; for many use strings.Builder:

var b strings.Builder
for i := 0; i < 1000; i++ {
    b.WriteString("x")
}
result := b.String()

Useful packages:

  • strings: Contains, Split, Join, ReplaceAll, ToUpper, Builder.
  • strconv: Itoa, Atoi, FormatFloat, ParseInt.
  • fmt: Sprintf, Sprintln.
  • unicode/utf8: low-level UTF-8 helpers.

Arrays

Arrays in Go are fixed length and the length is part of the type:

var a [3]int             // [0 0 0]
b := [3]int{1, 2, 3}
c := [...]int{1, 2, 3, 4} // length inferred: 4

Arrays are value types: assigning or passing them copies the whole array. You will rarely use arrays directly; you almost always want a slice.

Slices

A slice is a small struct: {pointer, length, capacity}. It refers to a backing array.

s := []int{1, 2, 3, 4, 5}
fmt.Println(len(s), cap(s)) // 5 5

t := s[1:4]                 // {2,3,4}, len=3, cap=4 (shares backing array!)
t[0] = 99                   // also modifies s[1]
fmt.Println(s)              // [1 99 3 4 5]

make, append, growth

s := make([]int, 0, 8)      // len 0, cap 8
s = append(s, 1, 2, 3)
s = append(s, more...)       // spread

// To copy instead of alias:
dst := make([]int, len(src))
copy(dst, src)

append may reallocate if cap isn't enough; the new capacity roughly doubles for small slices and grows by ~25% for large ones (implementation detail, not part of the spec).

Common slice idioms

// remove element at index i, preserving order:
a = append(a[:i], a[i+1:]...)

// remove element at index i, swapping with the last (O(1), order changes):
a[i] = a[len(a)-1]; a = a[:len(a)-1]

// insert v at index i:
a = append(a[:i], append([]int{v}, a[i:]...)...)

// reverse:
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

Maps

Maps are reference types implemented as hash tables.

m := map[string]int{"a": 1, "b": 2}
m["c"] = 3
v, ok := m["x"]      // v=0, ok=false
delete(m, "a")

for k, v := range m { // iteration order is RANDOMIZED on purpose
    fmt.Println(k, v)
}

A nil map can be read from (returns the zero value) but writing to it panics. Always create with a literal or make:

var m map[string]int
m["x"] = 1                     // PANIC

m = make(map[string]int)
m["x"] = 1                     // OK

Map keys must be comparable (no slices, maps, or functions as keys).

Structs

type Point struct {
    X, Y int
}

p := Point{X: 1, Y: 2}
q := Point{3, 4}                    // positional, fragile
fmt.Println(p.X, q.Y)

type Person struct {
    Name string
    Age  int
    Addr struct {                   // anonymous nested struct
        City string
    }
}

Structs are value types. Assigning copies the struct.

Embedding (composition)

type Animal struct{ Name string }
func (a Animal) Speak() string { return a.Name + " makes a sound" }

type Dog struct {
    Animal             // embedded — fields and methods are "promoted"
    Breed string
}

d := Dog{Animal{"Rex"}, "Lab"}
fmt.Println(d.Name, d.Speak()) // promoted access

This is how Go does "inheritance-like" reuse.

Pointers (preview)

p := &Point{1, 2}      // *Point
p.X = 10               // automatic dereference
fmt.Println((*p).Y)    // explicit dereference also works

Pointers are covered fully in Chapter 8.

Run the example

go run ./part1-language/examples/04_types