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.
graph TB
subgraph "Go Application Structure"
A[main.go] --> B[Package main]
B --> C[func main]
C --> D[Import Packages]
D --> E[Standard Library]
D --> F[Third-party Modules]
D --> G[Local Packages]
end
subgraph "Concurrency Model"
H[Goroutines] --> I[Channels]
I --> J[Select]
J --> K[Synchronisation]
end
subgraph "Build Pipeline"
L[go mod] --> M[go build]
M --> N[go test]
N --> O[Binary]
end
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
flowchart LR
subgraph "Goroutine Communication"
G1[Goroutine 1] -->|send| CH((Channel))
CH -->|receive| G2[Goroutine 2]
end
subgraph "Fan-out Pattern"
P[Producer] --> C1((Chan))
C1 --> W1[Worker 1]
C1 --> W2[Worker 2]
C1 --> W3[Worker 3]
end
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
errorinterface - 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
classDiagram
class Reader {
<<interface>>
+Read(p []byte) (n int, err error)
}
class Writer {
<<interface>>
+Write(p []byte) (n int, err error)
}
class ReadWriter {
<<interface>>
}
Reader <|-- ReadWriter
Writer <|-- ReadWriter
class File {
+Read(p []byte) (n int, err error)
+Write(p []byte) (n int, err error)
+Close() error
}
ReadWriter <|.. File
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:
- Docker - Containerising Go applications with multi-stage builds and minimal images
- Kubernetes - Deploying and scaling Go microservices in container orchestration
- Prometheus - Instrumenting Go applications with metrics and monitoring
- gRPC - Building high-performance RPC services in Go with Protocol Buffers
- PostgreSQL/Redis - Database patterns and caching strategies commonly used with Go
- GitHub Actions - CI/CD pipelines for building, testing, and deploying Go applications