Go Quick Start

Familiarize yourself with the major features of Go. Skip if you already know Go. You are not required to use Go for this project, though.

Syntax

Variables

// Declare variable types
var a int       // default is 0
var b string    // default is ""
// Assign values
a = 123
b = "hi"
// Declare and initialize
var c int = 123
d := "hi"       // Type is inferred automatically
// Assign multiple variables
a, c = c, a

An uninitialized value is guaranteed to be a zero value (0, false, "", nil, etc).

Statements

// If-else has no parentheses
if a != c {
    // ...
} else {
    // ...
}
// Switch does not fall through like C
switch a {
case 123:
    // ...
case 456:
    // ...
default:
    // ...
}
// Declare and use a variable in if
if err := f(); err != nil {
    // ...
}
// Loops
for i := 0; i < N; i++ {
    // ...
}
for zzz {
    // ...
}
for {
    if xxx {
        break
    }
    if yyy {
        continue
    }
}

Functions

func f(a int, b string) error {
    return nil
}
// Multiple return values
func g() (string, bool) {
    return "hi", false
}
// Assign multiple return values
s, b := g()
// Named return values
func h() (s string, b bool) {
    s = "hi"
    b = false
    return
}
// If a function returns an error, put error last
func foo() (string, error) {
    // ...
}

result, err := foo()
if err != nil {
    // handle error
} else {
    // use result
}

Data Types

Numeric Types

// Fixed size
bool, byte
int8, int16, int32, int64
uint8, uint16, uint32, uint64
float32, float64
// Size depends on system: 32-bit or 64-bit
int, uint
// Type conversion
a := 1  // int
var b int64
b = a           // compile error
b = int64(a)    // convert

Pointers/References

a := 1
var ptr *int    // declare pointer
ptr = &a        // take address
b := *ptr       // dereference

Go has GC, so pointer use is restricted, which includes arbitrary pointer arithmetic or storing pointers as other types. You won’t hit these issues if you avoid unsafe.

struct, method

type C struct {
    foo string,
    bar int,
}
// Initialize struct
c := C{foo: "hi", bar: 123}
// Access fields
c.bar = 456
bar1 := c.bar
// Pointer
pc := &c
bar2 := (*pc).bar   // dereference before using field
bar3 := pc.bar      // no need explicit dereference
// Normal function
func foo1(c *C) {
    c.foo = "hi"
}
// Method with pointer receiver
func (c *C) foo2() {
    c.foo = "hi"
}
// Method with value receiver
func (c C) bar() {
    println(c.bar)
}
// Call function
foo1(&c)
// Call methods
c.foo2()    // pointer
c.bar()     // value

Fixed-Size Arrays

var a [2]byte       // 2 elements, init to 0
var b [2][4]byte    // 2 elements, each is [4]byte
// Assign
b = [2][4]byte{{0, 0, 0, 0}, {0, 0, 0, 0}}
b[1][3] = 123

Strings

A string is just a some bytes without text encoding. It can convert to/from []byte.

type string struct {
    ptr *byte   // data
    len int     // length
}
s := "asdf"     // {ptr: "asdf", len: 4}
len(s)          // 4
// Strings are immutable
s[0] = 'b'      // compile error
// Slice without copying
s[2:4]          // {ptr: "df", len: 2}
// Concatenation creates a new string
s = "hi" + s

slice

In Go, slice actually has 2 unrelated uses shared by the same representation:

  1. A view of an array, with a pointer and length.
  2. A growable dynamic array, like C++ vector or Python list.
type sliceT struct {
    ptr *T
    len int
    cap int
}

These 2 uses are often confused, so they are explained separately.

a. Slice as an array view

// An array
var arr [8]byte = [8]byte{0, 1, 2, 3, 4, 5, 6, 7}
// Slice an array
s1 := arr[1:5]      // {ptr: &arr[1], len: 4, cap: 7}
// Iterate
for i := 0; i < len(s1); i++ {
    println(s1[i])  // 1 2 3 4
}
// Use `range` instead
for i := range s1 {
    println(s1[i])
}
for i, v := range s1 {
    println(v)
}
// Out of bounds causes panic
v := s1[4]

// Reslice, returns a new slice
s2 := s1[2:3]       // {ptr: &arr[3], len: 1, cap: 5}
for _, v := range s2 {
    println(v)      // 3
}

Bounds checks:

Usually, reslicing shrinks the len, although expansion is also allowed. When a slice is an array view, only len matters, cap can be ignored.

// shorthands
s[:]    // s[0:len(s)]
s[:j]   // s[0:j]
s[i:]   // s[i:len(s)]

b. Slice as a dynamic array

// Allocate array of len 2
arr1 := make([]byte, 2)     // {ptr: 0x100, len: 2, cap: 2}
// Allocate array with cap 2, len 0
arr2 := make([]byte, 0, 2)  // {ptr: 0x200, len: 0, cap: 2}
// Append elements
arr2 = append(arr2, 123)    // {ptr: 0x200, len: 1, cap: 2}
arr2 = append(arr2, 123)    // {ptr: 0x200, len: 2, cap: 2}
// Reallocate when cap is exceeded
arr2 = append(arr2, 123)    // {ptr: 0x300, len: 3, cap: 8}
// Append multiple elements
arr2 = append(arr2, 2, 3)   // {ptr: 0x300, len: 5, cap: 8}

Only dynamic slices can be appended. A slice that is an array view must not be appended, since it would modify the array.

Common slice operations

copy(dst, src)                      // copy data
s1 = slices.Clone(s)                // allocate and copy
s = slices.Concat(s1, s2, s2)       // concatenate
s = slices.Delete(s, i, j)          // delete s[i:j]
s = slices.Insert(s, i, e1, e2)     // insert at i
idx = slices.Index(s, v)            // find index
slices.Sort(s)                      // sort in place

A slice as an array view is only a reference and must not be appended. A slice as a dynamic array owns the array and can do any operation. A dynamic slice can be used as an array view, but not the reverse.

interface

An interface is a set of method rules. Any type that implements these methods can be used as the interface.

type Reader interface {
    Read(p []byte) (n int, err error)
}
// Type that implements the interface
type T struct { /* ... */ }
func (s *T) Read(p []byte) (n int, err error) { /* ... */ }
// A function that takes an interface as an argument
func useReader(r Reader) { /* ... */ }

obj := &T{}         // concrete type
useReader(obj)      // converted to interface

Calling methods via an interface does not depend on the concrete type, similar to abstract classes in OOP. An interface has 2 pointers:

type interfaceFoo struct {
    object   uintptr    // data pointer
    typeInfo uintptr    // type info
}

Converting a value to an interface stores the value pointer and its type info. Method calls use the type info to find the implementation.

An uninitialized interface has both pointers set to nil. This is different from converting a nil pointer value to an interface.

Recover the original type

var anything interface{}
anything = 1
anything = "asdf"

// Convert back to concrete type
integer, ok := anything.(int)   // ok = false
str, ok := anything.(string)    // ok = true
// Type switch
switch origin := anything.(type) {
case int:
    integer = origin
case string:
    str = origin
}

Tests

A test file ends with _test.go, e.g., foo_test.go. Test functions starts with Test and takes a *testing.T argument.

package foo

import "testing"

func TestSomething(t *testing.T) {
    if 1 + 2 != 3 {
        t.Fail()
    }
}

Run all test cases with this command: go test .