Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

Go

A statically typed, compiled language designed for simplicity, concurrency, and efficient systems programming.

Go

A statically typed, compiled language designed for simplicity, concurrency, and efficient systems programming.

Overview

Go (Golang) is a language developed by Google that combines the performance of compiled languages with the ease of use found in dynamic languages. It excels at building web services, CLI tools, and concurrent applications with its built-in support for goroutines and channels.

Build Pipelinego modgo buildgo testBinaryConcurrency ModelGoroutinesChannelsSelectSynchronisationGo Application Structuremain.goPackage mainfunc mainImport PackagesStandard LibraryThird-party ModulesLocal PackagesBuild Pipelinego modgo buildgo testBinaryConcurrency ModelGoroutinesChannelsSelectSynchronisationGo Application Structuremain.goPackage mainfunc mainImport PackagesStandard LibraryThird-party ModulesLocal Packages

Basic Syntax

Key Concepts

  • Statically typed: Variables have fixed types determined at compile time
  • Zero values: Variables are initialised to zero values if not explicitly set
  • Short declaration: := operator for declaring and initialising variables
  • Exported identifiers: Capitalised names are exported (public)
  • Package-based: Code is organised into packages

Variables and Types

// Variable declarations
var name string = "gopher"
var age int = 25
var active bool  // Zero value: false

// Short declaration (type inference)
count := 10
message := "Hello, World!"

// Multiple declarations
var (
    firstName string = "John"
    lastName  string = "Doe"
    score     int    = 100
)

// Constants
const Pi = 3.14159
const (
    StatusOK    = 200
    StatusError = 500
)

// Basic types
var (
    b    bool       = true
    s    string     = "text"
    i    int        = 42        // Platform-dependent size
    i8   int8       = 127
    i16  int16      = 32767
    i32  int32      = 2147483647
    i64  int64      = 9223372036854775807
    u    uint       = 42
    f32  float32    = 3.14
    f64  float64    = 3.141592653589793
    c64  complex64  = 1 + 2i
    c128 complex128 = 1 + 2i
)

// Type conversions (explicit)
var x int = 42
var y float64 = float64(x)
var z uint = uint(y)

Functions

// Basic function
func greet(name string) string {
    return "Hello, " + name
}

// Multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Named return values
func rectangle(width, height float64) (area, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return // Naked return
}

// Variadic functions
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// Anonymous functions and closures
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

// Deferred execution
func processFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close() // Executed when function returns

    // Process file...
    return nil
}

Control Flow

// If-else
if x > 10 {
    fmt.Println("Greater than 10")
} else if x > 5 {
    fmt.Println("Greater than 5")
} else {
    fmt.Println("5 or less")
}

// If with initialisation
if err := doSomething(); err != nil {
    log.Fatal(err)
}

// Switch
switch day {
case "Monday":
    fmt.Println("Start of week")
case "Friday":
    fmt.Println("End of week")
default:
    fmt.Println("Midweek")
}

// Switch with conditions
switch {
case score >= 90:
    grade = "A"
case score >= 80:
    grade = "B"
default:
    grade = "C"
}

// Type switch
switch v := i.(type) {
case int:
    fmt.Printf("Integer: %d\n", v)
case string:
    fmt.Printf("String: %s\n", v)
default:
    fmt.Printf("Unknown type\n")
}

// For loop (only loop in Go)
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// While-style loop
for count > 0 {
    count--
}

// Infinite loop
for {
    // Break to exit
    if done {
        break
    }
}

// Range over slice
for index, value := range slice {
    fmt.Printf("%d: %v\n", index, value)
}

// Range over map
for key, value := range myMap {
    fmt.Printf("%s: %v\n", key, value)
}

Collections

// Arrays (fixed size)
var arr [5]int
arr[0] = 1
numbers := [3]int{1, 2, 3}
auto := [...]int{1, 2, 3, 4, 5} // Size inferred

// Slices (dynamic)
slice := []int{1, 2, 3}
slice = append(slice, 4, 5)
subSlice := slice[1:3] // Elements 1 and 2

// Make slice with capacity
s := make([]int, 5, 10) // Length 5, capacity 10

// Maps
m := make(map[string]int)
m["one"] = 1
m["two"] = 2

// Map literal
ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
}

// Check if key exists
value, ok := ages["Charlie"]
if !ok {
    fmt.Println("Key not found")
}

// Delete from map
delete(ages, "Bob")

Goroutines and Channels

Key Concepts

  • Goroutines: Lightweight threads managed by the Go runtime
  • Channels: Typed conduits for communication between goroutines
  • CSP model: Communicating Sequential Processes paradigm
  • Select: Multiplex channel operations
Fan-out PatternProducerChanWorker 1Worker 2Worker 3Goroutine CommunicationsendreceiveGoroutine 1ChannelGoroutine 2Fan-out PatternProducerChanWorker 1Worker 2Worker 3Goroutine CommunicationsendreceiveGoroutine 1ChannelGoroutine 2

Goroutines

// Start a goroutine
go func() {
    fmt.Println("Running in goroutine")
}()

// Goroutine with parameters
go func(msg string) {
    fmt.Println(msg)
}("Hello")

// Wait for goroutines with WaitGroup
var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        fmt.Printf("Worker %d\n", id)
    }(i)
}

wg.Wait() // Block until all goroutines complete

Channels

// Create channels
ch := make(chan int)        // Unbuffered
buffered := make(chan int, 10) // Buffered with capacity 10

// Send and receive
ch <- 42      // Send
value := <-ch // Receive

// Channel directions (in function signatures)
func send(ch chan<- int) {
    ch <- 1 // Send-only
}

func receive(ch <-chan int) {
    v := <-ch // Receive-only
}

// Close channel
close(ch)

// Range over channel
for value := range ch {
    fmt.Println(value)
}

// Check if channel is closed
value, ok := <-ch
if !ok {
    fmt.Println("Channel closed")
}

Select Statement

// Multiplex channels
select {
case msg := <-ch1:
    fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
    fmt.Println("Received from ch2:", msg)
case ch3 <- 42:
    fmt.Println("Sent to ch3")
default:
    fmt.Println("No communication ready")
}

// Timeout pattern
select {
case result := <-ch:
    fmt.Println("Got result:", result)
case <-time.After(5 * time.Second):
    fmt.Println("Timeout!")
}

// Context cancellation
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

select {
case <-ctx.Done():
    fmt.Println("Context cancelled:", ctx.Err())
case result := <-ch:
    fmt.Println("Got result:", result)
}

Common Patterns

// Worker pool
func workerPool(jobs <-chan int, results chan<- int, numWorkers int) {
    var wg sync.WaitGroup

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- job * 2 // Process job
            }
        }()
    }

    wg.Wait()
    close(results)
}

// Fan-out, fan-in
func fanOut(input <-chan int, n int) []<-chan int {
    outputs := make([]<-chan int, n)
    for i := 0; i < n; i++ {
        outputs[i] = worker(input)
    }
    return outputs
}

func fanIn(inputs ...<-chan int) <-chan int {
    output := make(chan int)
    var wg sync.WaitGroup

    for _, ch := range inputs {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for v := range c {
                output <- v
            }
        }(ch)
    }

    go func() {
        wg.Wait()
        close(output)
    }()

    return output
}

// Pipeline
func pipeline(nums []int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

Error Handling

Key Concepts

  • Error as value: Errors are regular values implementing the error interface
  • Explicit handling: No exceptions; errors are returned and checked
  • Wrapping: Add context to errors while preserving the original
  • Sentinel errors: Predefined error values for comparison
// Basic error handling
result, err := someFunction()
if err != nil {
    return err
}

// Creating errors
import "errors"

err := errors.New("something went wrong")

// Formatted errors
import "fmt"

err := fmt.Errorf("failed to process %s: %v", filename, originalErr)

// Custom error types
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

// Using custom errors
func validate(user User) error {
    if user.Name == "" {
        return &ValidationError{
            Field:   "name",
            Message: "cannot be empty",
        }
    }
    return nil
}

Error Wrapping (Go 1.13+)

// Wrap error with context
if err != nil {
    return fmt.Errorf("failed to open config: %w", err)
}

// Unwrap errors
originalErr := errors.Unwrap(err)

// Check error type
var validationErr *ValidationError
if errors.As(err, &validationErr) {
    fmt.Printf("Validation failed on field: %s\n", validationErr.Field)
}

// Check error value
if errors.Is(err, os.ErrNotExist) {
    fmt.Println("File not found")
}

// Sentinel errors
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorised = errors.New("unauthorised")
    ErrInvalidInput = errors.New("invalid input")
)

func findUser(id string) (*User, error) {
    user, ok := users[id]
    if !ok {
        return nil, ErrNotFound
    }
    return user, nil
}

// Checking sentinel errors
user, err := findUser("123")
if errors.Is(err, ErrNotFound) {
    // Handle not found case
}

Panic and Recover

// Panic for unrecoverable errors
func mustGetEnv(key string) string {
    value := os.Getenv(key)
    if value == "" {
        panic(fmt.Sprintf("required env var %s not set", key))
    }
    return value
}

// Recover from panic
func safeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()

    // Potentially panicking code
    riskyOperation()
    return nil
}

Structs and Interfaces

Key Concepts

  • Structs: Composite types grouping fields together
  • Methods: Functions with receivers attached to types
  • Interfaces: Implicitly satisfied contracts
  • Embedding: Composition over inheritance
  • Pointer receivers: Modify the receiver value
«interface»Reader+Read(p []byte)(n int, err error)«interface»Writer+Write(p []byte)(n int, err error)«interface»ReadWriterFile+Read(p []byte)(n int, err error)+Write(p []byte)(n int, err error)+Close() : error«interface»Reader+Read(p []byte)(n int, err error)«interface»Writer+Write(p []byte)(n int, err error)«interface»ReadWriterFile+Read(p []byte)(n int, err error)+Write(p []byte)(n int, err error)+Close() : error

Structs

// Define struct
type User struct {
    ID        int
    Name      string
    Email     string
    CreatedAt time.Time
}

// Create instances
user1 := User{
    ID:    1,
    Name:  "Alice",
    Email: "alice@example.com",
}

user2 := User{ID: 2, Name: "Bob"} // Partial initialisation

// Access fields
fmt.Println(user1.Name)

// Pointers to structs
userPtr := &User{ID: 3, Name: "Charlie"}
fmt.Println(userPtr.Name) // Automatic dereference

// Anonymous structs
point := struct {
    X, Y int
}{10, 20}

// Struct tags
type APIResponse struct {
    Status  int    `json:"status"`
    Message string `json:"message"`
    Data    any    `json:"data,omitempty"`
}

Methods

// Value receiver (doesn't modify original)
func (u User) FullName() string {
    return u.Name
}

// Pointer receiver (can modify original)
func (u *User) UpdateEmail(email string) {
    u.Email = email
}

// Usage
user := User{Name: "Alice"}
fmt.Println(user.FullName())
user.UpdateEmail("new@example.com")

Interfaces

// Define interface
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// Compose interfaces
type ReadWriter interface {
    Reader
    Writer
}

// Implement interface (implicit)
type FileReader struct {
    filename string
}

func (fr *FileReader) Read(p []byte) (n int, err error) {
    // Implementation
    return len(p), nil
}

// Interface as parameter
func process(r Reader) error {
    buf := make([]byte, 1024)
    _, err := r.Read(buf)
    return err
}

// Empty interface (any type)
func printAny(v interface{}) { // or 'any' in Go 1.18+
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

// Type assertion
var i interface{} = "hello"

s := i.(string)        // Panic if wrong type
s, ok := i.(string)    // Safe assertion
if !ok {
    fmt.Println("Not a string")
}

Embedding

// Struct embedding
type Person struct {
    Name string
    Age  int
}

type Employee struct {
    Person    // Embedded
    JobTitle string
    Salary   float64
}

// Usage
emp := Employee{
    Person:   Person{Name: "Alice", Age: 30},
    JobTitle: "Developer",
    Salary:   75000,
}

fmt.Println(emp.Name)      // Promoted field
fmt.Println(emp.Person.Name) // Explicit access

// Interface embedding
type Closer interface {
    Close() error
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

HTTP Server/Client

Key Concepts

  • net/http: Standard library for HTTP operations
  • Handlers: Functions that process HTTP requests
  • ServeMux: HTTP request multiplexer (router)
  • Middleware: Functions that wrap handlers

HTTP Server

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

// Handler function
func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Hello, World!"))
}

// JSON response handler
func jsonHandler(w http.ResponseWriter, r *http.Request) {
    data := map[string]string{
        "message": "Hello, JSON!",
        "status":  "success",
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(data)
}

// Method-specific handlers (Go 1.22+ method patterns;
// ServeMux returns 405 automatically for unmatched methods)
func listUsersHandler(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(users)
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // Save user...
    w.WriteHeader(http.StatusCreated)
}

// Middleware
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()

    mux.HandleFunc("/", helloHandler)
    mux.HandleFunc("/api/data", jsonHandler)
    // Method and wildcard patterns (Go 1.22+)
    mux.HandleFunc("GET /api/users", listUsersHandler)
    mux.HandleFunc("POST /api/users", createUserHandler)
    mux.HandleFunc("GET /api/users/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := r.PathValue("id")
        json.NewEncoder(w).Encode(map[string]string{"id": id})
    })

    // Apply middleware
    handler := loggingMiddleware(mux)

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", handler))
}

HTTP Client

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "io"
    "net/http"
    "time"
)

// Simple GET request
func simpleGet(url string) ([]byte, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

// Custom client with timeout
var client = &http.Client{
    Timeout: 30 * time.Second,
}

// GET with custom client
func getWithClient(url string) (*http.Response, error) {
    return client.Get(url)
}

// POST JSON
func postJSON(url string, data interface{}) (*http.Response, error) {
    jsonData, err := json.Marshal(data)
    if err != nil {
        return nil, err
    }

    return client.Post(url, "application/json", bytes.NewBuffer(jsonData))
}

// Custom request with headers
func customRequest(ctx context.Context, method, url string, body io.Reader) (*http.Response, error) {
    req, err := http.NewRequestWithContext(ctx, method, url, body)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer token123")
    req.Header.Set("User-Agent", "MyApp/1.0")

    return client.Do(req)
}

// Decode JSON response
func fetchUser(id string) (*User, error) {
    resp, err := client.Get("https://api.example.com/users/" + id)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
    }

    var user User
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, err
    }

    return &user, nil
}

JSON Encoding/Decoding

Key Concepts

  • encoding/json: Standard library for JSON operations
  • Struct tags: Control JSON field names and behaviour
  • Marshal/Unmarshal: Convert between Go values and JSON

Encoding (Marshal)

import "encoding/json"

type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email,omitempty"`
    Password  string    `json:"-"`                // Never encode
    CreatedAt time.Time `json:"created_at"`
}

// Struct to JSON
user := User{
    ID:   1,
    Name: "Alice",
}

// Marshal to []byte
jsonBytes, err := json.Marshal(user)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(jsonBytes))

// Pretty print
jsonBytes, err := json.MarshalIndent(user, "", "  ")

// Encode to writer
json.NewEncoder(os.Stdout).Encode(user)

Decoding (Unmarshal)

// JSON to struct
jsonStr := `{"id": 1, "name": "Alice", "email": "alice@example.com"}`

var user User
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
    log.Fatal(err)
}

// Decode from reader
decoder := json.NewDecoder(resp.Body)
err := decoder.Decode(&user)

// Decode unknown structure
var data map[string]interface{}
err := json.Unmarshal([]byte(jsonStr), &data)

// Decode array
var users []User
err := json.Unmarshal(jsonBytes, &users)

Custom Marshalling

// Custom time format
type CustomTime struct {
    time.Time
}

func (ct CustomTime) MarshalJSON() ([]byte, error) {
    formatted := ct.Time.Format("2006-01-02")
    return []byte(`"` + formatted + `"`), nil
}

func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    t, err := time.Parse("2006-01-02", s)
    if err != nil {
        return err
    }
    ct.Time = t
    return nil
}

// Raw JSON handling
type Response struct {
    Status string          `json:"status"`
    Data   json.RawMessage `json:"data"` // Delay parsing
}

File Operations

Key Concepts

  • os package: Low-level file operations
  • io/ioutil: Convenient file utilities (deprecated in Go 1.16+)
  • bufio: Buffered I/O for efficiency
  • filepath: Platform-independent path manipulation

Reading Files

import (
    "bufio"
    "io"
    "os"
)

// Read entire file
data, err := os.ReadFile("config.json")
if err != nil {
    log.Fatal(err)
}

// Open and read
file, err := os.Open("data.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

content, err := io.ReadAll(file)

// Read line by line
file, err := os.Open("data.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    fmt.Println(line)
}

if err := scanner.Err(); err != nil {
    log.Fatal(err)
}

// Buffered reader
reader := bufio.NewReader(file)
for {
    line, err := reader.ReadString('\n')
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Print(line)
}

Writing Files

// Write entire file
data := []byte("Hello, World!")
err := os.WriteFile("output.txt", data, 0644)
if err != nil {
    log.Fatal(err)
}

// Create and write
file, err := os.Create("output.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

file.WriteString("Hello, World!\n")
file.Write([]byte("More data\n"))

// Buffered writer
writer := bufio.NewWriter(file)
writer.WriteString("Buffered content\n")
writer.Flush() // Don't forget to flush!

// Append to file
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    log.Fatal(err)
}
defer file.Close()

file.WriteString("New log entry\n")

File Operations

import (
    "os"
    "path/filepath"
)

// File info
info, err := os.Stat("file.txt")
if err != nil {
    if os.IsNotExist(err) {
        fmt.Println("File does not exist")
    }
}
fmt.Printf("Size: %d bytes\n", info.Size())
fmt.Printf("Is directory: %v\n", info.IsDir())
fmt.Printf("Modified: %v\n", info.ModTime())

// Create directory
err := os.Mkdir("newdir", 0755)
err := os.MkdirAll("path/to/nested/dir", 0755)

// Remove
err := os.Remove("file.txt")
err := os.RemoveAll("directory")

// Rename/move
err := os.Rename("old.txt", "new.txt")

// Copy file
func copyFile(src, dst string) error {
    source, err := os.Open(src)
    if err != nil {
        return err
    }
    defer source.Close()

    dest, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer dest.Close()

    _, err = io.Copy(dest, source)
    return err
}

// Walk directory
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(path)
    return nil
})

// Glob pattern matching
matches, err := filepath.Glob("*.txt")

Testing

Key Concepts

  • testing package: Built-in testing framework
  • Table-driven tests: Data-driven test pattern
  • Benchmarks: Performance measurement
  • Examples: Documented, testable examples
  • Subtests: Nested test organisation

Basic Tests

// math_test.go
package math

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

func TestAddNegative(t *testing.T) {
    result := Add(-1, 1)
    if result != 0 {
        t.Errorf("Add(-1, 1) = %d; want 0", result)
    }
}

Table-Driven Tests

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"zero", 0, 0, 0},
        {"mixed", -1, 2, 1},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Test Helpers and Setup

// TestMain for setup/teardown
func TestMain(m *testing.M) {
    // Setup
    setup()

    // Run tests
    code := m.Run()

    // Teardown
    teardown()

    os.Exit(code)
}

// Helper function
func assertEqual(t *testing.T, got, want interface{}) {
    t.Helper() // Mark as helper (better error reporting)
    if got != want {
        t.Errorf("got %v; want %v", got, want)
    }
}

// Parallel tests
func TestParallel(t *testing.T) {
    t.Parallel()
    // Test code...
}

Benchmarks

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

// Benchmark with setup
func BenchmarkProcess(b *testing.B) {
    data := generateData()

    b.ResetTimer() // Don't count setup time

    for i := 0; i < b.N; i++ {
        Process(data)
    }
}

HTTP Testing

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/api/users", nil)
    rec := httptest.NewRecorder()

    handler := http.HandlerFunc(usersHandler)
    handler.ServeHTTP(rec, req)

    if rec.Code != http.StatusOK {
        t.Errorf("status code = %d; want %d", rec.Code, http.StatusOK)
    }

    // Check response body
    expected := `{"users":[]}`
    if rec.Body.String() != expected {
        t.Errorf("body = %s; want %s", rec.Body.String(), expected)
    }
}

// Test server
func TestAPIClient(t *testing.T) {
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"status": "ok"}`))
    }))
    defer server.Close()

    // Use server.URL in tests
    resp, err := http.Get(server.URL + "/api/test")
    // ...
}

Running Tests

# Run all tests
go test

# Run with verbose output
go test -v

# Run specific test
go test -run TestAdd

# Run tests in all packages
go test ./...

# Run benchmarks
go test -bench=.

# Generate coverage report
go test -cover
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

# Race detector
go test -race

CLI Patterns

Key Concepts

  • flag package: Standard library for command-line flags
  • os.Args: Direct access to arguments
  • cobra: Popular framework for complex CLIs
  • Subcommands: Nested command structure

Flag Package

package main

import (
    "flag"
    "fmt"
)

func main() {
    // Define flags
    name := flag.String("name", "World", "Name to greet")
    count := flag.Int("count", 1, "Number of greetings")
    verbose := flag.Bool("verbose", false, "Enable verbose output")

    // Parse flags
    flag.Parse()

    // Use flag values (dereference pointers)
    for i := 0; i < *count; i++ {
        if *verbose {
            fmt.Printf("Greeting %d: ", i+1)
        }
        fmt.Printf("Hello, %s!\n", *name)
    }

    // Non-flag arguments
    args := flag.Args()
    fmt.Println("Remaining args:", args)
}

// Usage: go run main.go -name=Alice -count=3 -verbose extra args

Flag with Custom Types

// Custom flag type
type stringSlice []string

func (s *stringSlice) String() string {
    return fmt.Sprintf("%v", *s)
}

func (s *stringSlice) Set(value string) error {
    *s = append(*s, value)
    return nil
}

func main() {
    var files stringSlice
    flag.Var(&files, "file", "Files to process (can be repeated)")
    flag.Parse()

    fmt.Println("Files:", files)
}

// Usage: go run main.go -file=a.txt -file=b.txt

Cobra Framework

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var cfgFile string
var verbose bool

// Root command
var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "A brief description of your application",
    Long:  `A longer description that spans multiple lines.`,
}

// Subcommand
var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "Start the server",
    Run: func(cmd *cobra.Command, args []string) {
        port, _ := cmd.Flags().GetInt("port")
        fmt.Printf("Starting server on port %d\n", port)
    },
}

// Another subcommand
var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "Print the version number",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("v1.0.0")
    },
}

func init() {
    // Persistent flags (available to all commands)
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")

    // Local flags (only for serve command)
    serveCmd.Flags().IntP("port", "p", 8080, "Port to listen on")

    // Add subcommands
    rootCmd.AddCommand(serveCmd)
    rootCmd.AddCommand(versionCmd)
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

// Usage: myapp serve --port 3000
//        myapp version
//        myapp --config=config.yaml serve

Common Patterns

// Environment variable fallback
func getConfig(flagValue, envKey, defaultValue string) string {
    if flagValue != "" {
        return flagValue
    }
    if envValue := os.Getenv(envKey); envValue != "" {
        return envValue
    }
    return defaultValue
}

// Required flags
func main() {
    name := flag.String("name", "", "Name (required)")
    flag.Parse()

    if *name == "" {
        fmt.Fprintln(os.Stderr, "Error: -name is required")
        flag.Usage()
        os.Exit(1)
    }
}

// Custom usage message
flag.Usage = func() {
    fmt.Fprintf(os.Stderr, "Usage: %s [options] <files...>\n\n", os.Args[0])
    fmt.Fprintln(os.Stderr, "Options:")
    flag.PrintDefaults()
}

Package Management

Key Concepts

  • Go modules: Dependency management system (Go 1.11+)
  • go.mod: Module definition file
  • go.sum: Checksums for dependencies
  • Semantic versioning: Version format used by Go modules

Module Basics

# Initialise new module
go mod init github.com/username/projectname

# Download dependencies
go mod download

# Add missing and remove unused dependencies
go mod tidy

# Verify dependencies
go mod verify

# Show module graph
go mod graph

# Show why dependency is needed
go mod why github.com/some/package

go.mod File

module github.com/username/myproject

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/spf13/cobra v1.7.0
    github.com/stretchr/testify v1.8.4
)

require (
    // Indirect dependencies (managed automatically)
    github.com/inconshreveable/mousetrap v1.1.0 // indirect
    github.com/spf13/pflag v1.0.5 // indirect
)

// Replace directive (for local development)
replace github.com/original/package => ../local/package

// Exclude specific version
exclude github.com/some/package v1.2.3

Dependency Management

# Add dependency
go get github.com/gin-gonic/gin

# Add specific version
go get github.com/gin-gonic/gin@v1.9.1

# Update dependency
go get -u github.com/gin-gonic/gin

# Update all dependencies
go get -u ./...

# Update to latest patch version
go get -u=patch ./...

# Remove dependency
# (Remove import, then run go mod tidy)

# List all dependencies
go list -m all

# List available versions
go list -m -versions github.com/gin-gonic/gin

# Check for updates
go list -m -u all

Vendoring

# Create vendor directory
go mod vendor

# Build using vendor
go build -mod=vendor

# Verify vendor contents
go mod verify

Private Modules

# Configure private module paths
go env -w GOPRIVATE=github.com/mycompany/*

# Multiple patterns
go env -w GOPRIVATE=github.com/mycompany/*,gitlab.internal/*

# Use SSH for private repos
git config --global url."git@github.com:".insteadOf "https://github.com/"

Quick Reference

Category Command/Pattern Description
Build go build Compile package
go run main.go Compile and run
go install Compile and install
Test go test Run tests
go test -v Verbose output
go test -cover Coverage report
go test -bench=. Run benchmarks
Modules go mod init Initialise module
go mod tidy Clean up dependencies
go get pkg@version Add/update dependency
Tools go fmt Format code
go vet Static analysis
go doc pkg Show documentation
goimports Manage imports
Variables var x int Explicit declaration
x := 10 Short declaration
const X = 10 Constant
Functions func name() {} No return
func name() T {} Single return
func name() (T, error) {} Multiple returns
Concurrency go func(){}() Start goroutine
ch := make(chan T) Create channel
ch <- v Send to channel
v := <-ch Receive from channel
Errors errors.New("msg") Create error
fmt.Errorf("x: %w", err) Wrap error
errors.Is(err, target) Compare errors
errors.As(err, &target) Type check error

Common Issues and Solutions

Issue Solution
undefined: X Check export (capitalise first letter) or import the package
cannot use X as type Y Use explicit type conversion: Y(x)
multiple-value in single-value context Assign all return values: val, err := func()
imported and not used Remove unused import or use _ to ignore
declared but not used Use the variable or assign to _
nil pointer dereference Check for nil before dereferencing pointers
deadlock Ensure channels have receivers/senders; use buffered channels
data race Use mutexes, channels, or atomic operations for shared state
go.mod not found Run go mod init module-name in project root
module not found Run go mod tidy or go get module@version
ambiguous import Use explicit import path or alias: import alias "pkg"
cannot find module providing package Check GOPRIVATE for private repos; verify network access
checksum mismatch Delete go.sum and run go mod tidy
context deadline exceeded Increase timeout or optimise operation
too many open files Close files/connections properly; increase ulimit

Related Topics

The following topics complement Go development and would make excellent additions to your cheatsheet collection:

  1. Docker - Containerising Go applications with multi-stage builds and minimal images
  2. Kubernetes - Deploying and scaling Go microservices in container orchestration
  3. Prometheus - Instrumenting Go applications with metrics and monitoring
  4. gRPC - Building high-performance RPC services in Go with Protocol Buffers
  5. PostgreSQL/Redis - Database patterns and caching strategies commonly used with Go
  6. GitHub Actions - CI/CD pipelines for building, testing, and deploying Go applications