A Comprehensive Tour of Go
A Comprehensive Tour of Go
Section titled “A Comprehensive Tour of Go”A practical guide covering the most important Go concepts with real-world examples from Go by Example and the official Go Tour.
Table of Contents
Section titled “Table of Contents”Priority Topics
Section titled “Priority Topics”- Getting Started & Package Basics
- Variables & Basic Types
- Structs - Organizing Data
- Error Handling
- Reading Files
- Writing Files
- Working with Directories
- Goroutines - Concurrent Execution
- Channels - Communicating Between Goroutines
- HTTP Client - GET & POST Requests
- HTTP Server
- Context for Request Management
- JSON Encoding & Decoding
1. Getting Started & Package Basics
Section titled “1. Getting Started & Package Basics”Hello World
Section titled “Hello World”Every Go program starts with a package declaration and imports.
package main
import "fmt"
func main() { fmt.Println("hello world")}Key Points:
package maindeclares the main package (entry point for executables)import "fmt"imports the formatting I/O packagemain()function is the program’s entry point
Running Go Programs:
# Run directly without buildinggo run hello-world.go
# Build an executable binarygo build hello-world.go./hello-world2. Variables & Basic Types
Section titled “2. Variables & Basic Types”Variable Declaration
Section titled “Variable Declaration”In Go, variables are explicitly declared and used by the compiler to check type-correctness of function calls.
package main
import "fmt"
func main() { // Basic var declaration with initialization var a = "initial" fmt.Println(a)
// Multiple variables at once var b, c int = 1, 2 fmt.Println(b, c)
// Type inference - Go infers the type var d = true fmt.Println(d)
// Zero values - variables without initialization var e int fmt.Println(e) // Prints: 0
// Shorthand := syntax (only inside functions) f := "apple" fmt.Println(f)}Output:
initial1 2true0appleKey Concepts:
- Use
varfor explicit declarations - Go infers types from initialization values
- Zero-valued: Variables without initialization get default values (0 for int, "" for string, false for bool)
:=shorthand combines declaration and initialization (function-scope only)
3. Structs - Organizing Data
Section titled “3. Structs - Organizing Data”Structs are typed collections of fields used to group related data.
Defining Structs
Section titled “Defining Structs”package main
import "fmt"
type person struct { name string age int}
// Constructor function (idiomatic pattern)func newPerson(name string) *person { p := person{name: name} p.age = 42 return &p // Safe to return pointer to local variable}
func main() { // Positional initialization fmt.Println(person{"Bob", 20})
// Named field initialization fmt.Println(person{name: "Alice", age: 30})
// Omitted fields are zero-valued fmt.Println(person{name: "Fred"}) // age = 0
// Pointer to struct fmt.Println(&person{name: "Ann", age: 40})
// Using constructor fmt.Println(newPerson("Jon"))
// Access fields with dot notation s := person{name: "Sean", age: 50} fmt.Println(s.name)
// Pointers automatically dereference sp := &s fmt.Println(sp.age)
// Structs are mutable sp.age = 51 fmt.Println(sp.age)
// Anonymous structs (for single-use cases) dog := struct { name string isGood bool }{ "Rex", true, } fmt.Println(dog)}4. Error Handling
Section titled “4. Error Handling”Basic Errors
Section titled “Basic Errors”In Go, errors are the last return value and have type error, a built-in interface. This differs from exception-based languages.
package main
import ( "errors" "fmt")
// Function that returns an errorfunc f(arg int) (int, error) { if arg == 42 { return -1, errors.New("can't work with 42") } return arg + 3, nil // nil means no error}
// Sentinel errors (pre-declared error variables)var ErrOutOfTea = errors.New("no more tea available")var ErrPower = errors.New("can't boil water")
func makeTea(arg int) error { if arg == 2 { return ErrOutOfTea } else if arg == 4 { // Error wrapping with %w return fmt.Errorf("making tea: %w", ErrPower) } return nil}
func main() { // Check errors inline for i := 0; i < 3; i++ { if r, e := f(i); e != nil { fmt.Println("f failed:", e) } else { fmt.Println("f worked:", r) } }
// Check specific errors with errors.Is() for i := range 5 { if err := makeTea(i); err != nil { // errors.Is() checks the error chain if errors.Is(err, ErrOutOfTea) { fmt.Println("We should buy new tea!") } else if errors.Is(err, ErrPower) { fmt.Println("Now it is dark.") } else { fmt.Printf("unknown error: %s\n", err) } continue } fmt.Println("Tea is ready!") }}Key Points:
- Return errors as the last value
nilindicates no error- Use
errors.New()for simple errors - Sentinel errors: Pre-declared error variables for specific conditions
- Error wrapping: Use
fmt.Errorf()with%wto add context errors.Is(): Check for specific errors in the error chain
Custom Errors
Section titled “Custom Errors”Create custom error types by implementing the Error() method.
package main
import ( "errors" "fmt")
// Custom error type with additional fieldstype argError struct { arg int message string}
func (e *argError) Error() string { return fmt.Sprintf("%d - %s", e.arg, e.message)}
func f(arg int) (int, error) { if arg == 42 { return -1, &argError{arg, "can't work with it"} } return arg + 3, nil}
func main() { _, err := f(42)
// Type-specific error handling with errors.As() var ae *argError if errors.As(err, &ae) { fmt.Println(ae.arg) // 42 fmt.Println(ae.message) // can't work with it } else { fmt.Println("err doesn't match argError") }}Key Points:
- Custom errors typically use the “Error” suffix
- Implement
Error() stringmethod errors.As(): Check error type and convert to that type
Panic & Recover
Section titled “Panic & Recover”Panic stops normal execution and causes program crash. Recover catches panics.
package main
import ( "os" "path/filepath")
func main() { // Panic with a message panic("a problem")
// Common use: panic on unexpected errors path := filepath.Join(os.TempDir(), "file") _, err := os.Create(path) if err != nil { panic(err) }}Output:
panic: a problem
goroutine 1 [running]:main.main() /.../panic.go:12 +0x47...exit status 2Use panic for:
- Unrecoverable errors during normal operation
- Programming errors that should never happen
Go Philosophy: Unlike exception-based languages, Go prefers returning errors over panics for most error handling.
Recover
Section titled “Recover”package main
import "fmt"
func mayPanic() { panic("a problem")}
func main() { // Recover must be called in a deferred function defer func() { if r := recover(); r != nil { fmt.Println("Recovered. Error:\n", r) } }()
mayPanic() fmt.Println("After mayPanic()") // This never executes}Output:
Recovered. Error: a problemKey Points:
recover()must be called inside adeferblock- Catches panics and prevents program crash
- Useful for servers that shouldn’t crash on individual errors
- Code after panic doesn’t execute, but program continues
Defer for Cleanup
Section titled “Defer for Cleanup”Defer ensures a function call is performed later, usually for cleanup.
package main
import ( "fmt" "os")
func createFile(p string) *os.File { fmt.Println("creating") f, err := os.Create(p) if err != nil { panic(err) } return f}
func writeFile(f *os.File) { fmt.Println("writing") fmt.Fprintln(f, "data")}
func closeFile(f *os.File) { fmt.Println("closing") err := f.Close() // Always check errors, even in deferred functions if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) }}
func main() { f := createFile("/tmp/defer.txt") defer closeFile(f) // Will execute at function end writeFile(f)}Output:
creatingwritingclosingKey Points:
- Defer executes at function end
- Declare cleanup immediately after resource acquisition
- Check errors even in deferred functions
- Similar to
finallyin other languages
5. Reading Files
Section titled “5. Reading Files”Multiple Approaches to Reading Files
Section titled “Multiple Approaches to Reading Files”package main
import ( "bufio" "fmt" "io" "os")
func check(e error) { if e != nil { panic(e) }}
func main() { // 1. Read entire file into memory dat, err := os.ReadFile("/tmp/dat") check(err) fmt.Print(string(dat))
// 2. Open file for more control f, err := os.Open("/tmp/dat") check(err) defer f.Close()
// 3. Read specific number of bytes b1 := make([]byte, 5) n1, err := f.Read(b1) check(err) fmt.Printf("%d bytes: %s\n", n1, string(b1[:n1]))
// 4. Seek to a position in the file o2, err := f.Seek(6, io.SeekStart) // Absolute position check(err) b2 := make([]byte, 2) n2, err := f.Read(b2) check(err) fmt.Printf("%d bytes @ %d: %s\n", n2, o2, string(b2[:n2]))
// 5. Seek relative to current position _, err = f.Seek(4, io.SeekCurrent) check(err)
// 6. ReadAtLeast ensures minimum bytes o3, err := f.Seek(6, io.SeekStart) check(err) b3 := make([]byte, 2) n3, err := io.ReadAtLeast(f, b3, 2) check(err) fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))
// 7. Rewind to beginning _, err = f.Seek(0, io.SeekStart) check(err)
// 8. Buffered reading (efficient for multiple small reads) r4 := bufio.NewReader(f) b4, err := r4.Peek(5) // Look ahead without consuming check(err) fmt.Printf("5 bytes: %s\n", string(b4))}Key Methods:
os.ReadFile(): Read entire file into memory (simplest)os.Open()+Read(): Controlled reading with specific byte counts- Seeking modes:
io.SeekStart: Absolute position from beginningio.SeekCurrent: Relative to current positionio.SeekEnd: Relative to end (supports negative offsets)
io.ReadAtLeast(): Safer reading with minimum byte guaranteesbufio.NewReader(): Buffered reading for efficiencyPeek(): Examine data without consuming it
6. Writing Files
Section titled “6. Writing Files”Multiple Writing Techniques
Section titled “Multiple Writing Techniques”package main
import ( "bufio" "fmt" "os")
func check(e error) { if e != nil { panic(e) }}
func main() { // 1. Write entire file at once d1 := []byte("hello\ngo\n") err := os.WriteFile("/tmp/dat1", d1, 0644) check(err)
// 2. Open file for more control f, err := os.Create("/tmp/dat2") check(err) // Always defer Close immediately after opening defer f.Close()
// 3. Write byte slices d2 := []byte{115, 111, 109, 101, 10} n2, err := f.Write(d2) check(err) fmt.Printf("wrote %d bytes\n", n2)
// 4. Write strings directly n3, err := f.WriteString("writes\n") check(err) fmt.Printf("wrote %d bytes\n", n3)
// 5. Sync - flush writes to stable storage f.Sync()
// 6. Buffered writing (best for multiple writes) w := bufio.NewWriter(f) n4, err := w.WriteString("buffered\n") check(err) fmt.Printf("wrote %d bytes\n", n4)
// Flush to apply buffered writes w.Flush()}Key Methods:
os.WriteFile(): Write entire file at once (simplest)os.Create(): Open file for writing- Idiomatic pattern:
defer f.Close()immediately after opening Write(): Write byte slicesWriteString(): Write strings directlySync(): Flush writes to diskbufio.NewWriter(): Buffered writing for performance- Always call
Flush()to apply buffered operations
- Always call
7. Working with Directories
Section titled “7. Working with Directories”package main
import ( "fmt" "os" "path/filepath")
func check(e error) { if e != nil { panic(e) }}
func main() { // Create a single directory err := os.Mkdir("subdir", 0755) check(err) defer os.RemoveAll("subdir") // Cleanup
// Create nested directories (like mkdir -p) err = os.MkdirAll("subdir/parent/child", 0755) check(err)
// Read directory contents c, err := os.ReadDir("subdir/parent") check(err) fmt.Println("Listing subdir/parent") for _, entry := range c { fmt.Println(" ", entry.Name(), entry.IsDir()) }
// Change directory err = os.Chdir("subdir/parent/child") check(err)
// Read current directory c, err = os.ReadDir(".") check(err) fmt.Println("Listing subdir/parent/child") for _, entry := range c { fmt.Println(" ", entry.Name(), entry.IsDir()) }
// Change back err = os.Chdir("../../..") check(err)
// Recursive directory traversal fmt.Println("Visiting subdir") err = filepath.WalkDir("subdir", visit) check(err)}
func visit(path string, d os.DirEntry, err error) error { if err != nil { return err } fmt.Println(" ", path, d.IsDir()) return nil}Key Functions:
os.Mkdir(): Create single directoryos.MkdirAll(): Create directory hierarchy (likemkdir -p)os.ReadDir(): List directory contents asos.DirEntrysliceos.Chdir(): Change working directoryos.RemoveAll(): Delete directory tree recursivelyfilepath.WalkDir(): Traverse directories with callback function
8. Goroutines - Concurrent Execution
Section titled “8. Goroutines - Concurrent Execution”A goroutine is a lightweight thread of execution enabling concurrent programming.
package main
import ( "fmt" "time")
func f(from string) { for i := 0; i < 3; i++ { fmt.Println(from, ":", i) }}
func main() { // Standard synchronous function call f("direct")
// Launch goroutine with go keyword go f("goroutine")
// Anonymous function as goroutine go func(msg string) { fmt.Println(msg) }("going")
// Wait for goroutines (use WaitGroup in production) time.Sleep(time.Second) fmt.Println("done")}Key Points:
gokeyword launches functions concurrently- Works with named and anonymous functions
- Goroutines execute concurrently, output may be interleaved
- Much lighter than OS threads
- Use WaitGroup for proper synchronization (not
time.Sleep)
9. Channels - Communicating Between Goroutines
Section titled “9. Channels - Communicating Between Goroutines”Basic Channels
Section titled “Basic Channels”Channels are the pipes that connect concurrent goroutines. Send values from one goroutine, receive in another.
package main
import "fmt"
func main() { // Create a channel messages := make(chan string)
// Send value to channel from goroutine go func() { messages <- "ping" }()
// Receive value from channel msg := <-messages fmt.Println(msg)}Output: ping
Key Points:
- Create with
make(chan val-type) - Send:
channel <- value - Receive:
value := <-channel - Blocking by default: Sends and receives block until both sender and receiver are ready
- Provides synchronization without additional mechanisms
Buffered Channels
Section titled “Buffered Channels”Buffered channels accept a limited number of values without a corresponding receiver.
package main
import "fmt"
func main() { // Create buffered channel with capacity 2 messages := make(chan string, 2)
// Send without blocking (buffer has space) messages <- "buffered" messages <- "channel"
// Receive later fmt.Println(<-messages) fmt.Println(<-messages)}Output:
bufferedchannelKey Difference:
- Unbuffered: Requires immediate receiver
- Buffered: Stores values up to capacity, enables asynchronous communication
Select Statement
Section titled “Select Statement”Select lets you wait on multiple channel operations simultaneously.
package main
import ( "fmt" "time")
func main() { c1 := make(chan string) c2 := make(chan string)
// Goroutine 1: sends after 1 second go func() { time.Sleep(1 * time.Second) c1 <- "one" }()
// Goroutine 2: sends after 2 seconds go func() { time.Sleep(2 * time.Second) c2 <- "two" }()
// Select waits on multiple channels for range 2 { select { case msg1 := <-c1: fmt.Println("received", msg1) case msg2 := <-c2: fmt.Println("received", msg2) } }}Output:
received onereceived twoKey Benefits:
- Wait on multiple channels concurrently
- Total execution ~2 seconds (not 3) due to concurrent execution
- Powerful when combined with goroutines and channels
WaitGroups
Section titled “WaitGroups”WaitGroups coordinate the completion of multiple goroutines.
package main
import ( "fmt" "sync" "time")
func worker(id int) { fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id)}
func main() { var wg sync.WaitGroup
// Launch 5 workers for i := 1; i <= 5; i++ { wg.Add(1) // Increment counter
go func(id int) { defer wg.Done() // Decrement counter when done worker(id) }(i) }
// Block until all workers complete wg.Wait()}Important:
wg.Add(1): Increment counter before launching goroutinewg.Done(): Decrement counter when goroutine completeswg.Wait(): Block until counter reaches zero- Pass WaitGroup by pointer when passing to functions
- Output order is non-deterministic (concurrent execution)
Limitation: No built-in error propagation. For advanced cases, use golang.org/x/sync/errgroup
Worker Pools
Section titled “Worker Pools”Worker pools process jobs concurrently with a fixed number of workers.
package main
import ( "fmt" "time")
func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Println("worker", id, "started job", j) time.Sleep(time.Second) fmt.Println("worker", id, "finished job", j) results <- j * 2 }}
func main() { const numJobs = 5 jobs := make(chan int, numJobs) results := make(chan int, numJobs)
// Start 3 workers for w := 1; w <= 3; w++ { go worker(w, jobs, results) }
// Send 5 jobs for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs)
// Collect results for a := 1; a <= numJobs; a++ { <-results }}Efficiency:
- ~2 seconds total for 5 seconds of work
- 3 workers process 5 jobs concurrently
- Demonstrates parallelization benefits
Pattern:
- Buffered channels for jobs and results
- Fixed number of workers
- Close jobs channel when done sending
- Workers range over jobs channel
10. HTTP Client - GET & POST Requests
Section titled “10. HTTP Client - GET & POST Requests”GET Requests
Section titled “GET Requests”package main
import ( "bufio" "fmt" "net/http")
func main() { // Simple GET request resp, err := http.Get("https://gobyexample.com") if err != nil { panic(err) } defer resp.Body.Close()
// Print response status fmt.Println("Response status:", resp.Status)
// Read response body line by line scanner := bufio.NewScanner(resp.Body) for i := 0; scanner.Scan() && i < 5; i++ { fmt.Println(scanner.Text()) }
if err := scanner.Err(); err != nil { panic(err) }}Key Points:
http.Get(): Convenient shortcut for GET requests- Always close response body:
defer resp.Body.Close() - Access status:
resp.Status - Read body with
bufio.Scannerfor line-by-line processing
POST Requests with JSON
Section titled “POST Requests with JSON”package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type RequestData struct { Name string `json:"name"` Email string `json:"email"`}
type ResponseData struct { Success bool `json:"success"` Message string `json:"message"`}
func main() { // Prepare request data data := RequestData{ Name: "John Doe", Email: "john@example.com", }
// Encode to JSON jsonData, err := json.Marshal(data) if err != nil { panic(err) }
// POST request resp, err := http.Post( "https://httpbin.org/post", "application/json", bytes.NewBuffer(jsonData), ) if err != nil { panic(err) } defer resp.Body.Close()
// Decode response var result ResponseData if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) }
fmt.Printf("Response: %+v\n", result)}For More Control:
package main
import ( "bytes" "net/http")
func main() { client := &http.Client{}
req, err := http.NewRequest("POST", "https://api.example.com/data", bytes.NewBuffer(jsonData)) if err != nil { panic(err) }
// Set headers req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer token123")
// Execute request resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close()}11. HTTP Server
Section titled “11. HTTP Server”package main
import ( "fmt" "net/http")
// Handler functions take ResponseWriter and Requestfunc hello(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "hello\n")}
func headers(w http.ResponseWriter, req *http.Request) { // Access request headers for name, headers := range req.Header { for _, h := range headers { fmt.Fprintf(w, "%v: %v\n", name, h) } }}
func main() { // Register handlers http.HandleFunc("/hello", hello) http.HandleFunc("/headers", headers)
// Start server on port 8090 http.ListenAndServe(":8090", nil)}Usage:
# Run servergo run http-server.go &
# Test endpointscurl localhost:8090/hello# Output: hello
curl localhost:8090/headers# Output: User-Agent: curl/7.79.1# Accept: */*Key Concepts:
- Handlers: Functions implementing
func(http.ResponseWriter, *http.Request) http.HandleFunc(): Register route handlershttp.ListenAndServe(): Start server on specified porthttp.ResponseWriter: Write HTTP responses*http.Request: Access request data (headers, body, etc.)
12. Context for Request Management
Section titled “12. Context for Request Management”Context carries deadlines, cancellation signals, and request-scoped values across API boundaries and goroutines.
package main
import ( "fmt" "net/http" "time")
func hello(w http.ResponseWriter, req *http.Request) { // Get context from request ctx := req.Context() fmt.Println("server: hello handler started") defer fmt.Println("server: hello handler ended")
// Monitor context cancellation select { case <-time.After(10 * time.Second): fmt.Fprintf(w, "hello\n") case <-ctx.Done(): // Client disconnected or timeout err := ctx.Err() fmt.Println("server:", err) http.Error(w, err.Error(), http.StatusInternalServerError) }}
func main() { http.HandleFunc("/hello", hello) http.ListenAndServe(":8090", nil)}How It Works:
- Each HTTP request has a context via
req.Context() - Context cancels when client disconnects
- Use
selectwithctx.Done()to handle cancellation ctx.Err()returns cancellation reason- Prevents wasting resources on abandoned requests
Test with:
curl localhost:8090/hello# Press Ctrl+C before 10 seconds# Server logs: server: context canceled13. JSON Encoding & Decoding
Section titled “13. JSON Encoding & Decoding”Go provides built-in JSON support through encoding/json.
package main
import ( "encoding/json" "fmt" "os")
// Structs for JSON mappingtype response1 struct { Page int Fruits []string}
type response2 struct { Page int `json:"page"` Fruits []string `json:"fruits"`}
func main() { // Encode basic types bolB, _ := json.Marshal(true) fmt.Println(string(bolB)) // true
intB, _ := json.Marshal(1) fmt.Println(string(intB)) // 1
fltB, _ := json.Marshal(2.34) fmt.Println(string(fltB)) // 2.34
strB, _ := json.Marshal("gopher") fmt.Println(string(strB)) // "gopher"
// Encode slices slcD := []string{"apple", "peach", "pear"} slcB, _ := json.Marshal(slcD) fmt.Println(string(slcB)) // ["apple","peach","pear"]
// Encode maps mapD := map[string]int{"apple": 5, "lettuce": 7} mapB, _ := json.Marshal(mapD) fmt.Println(string(mapB)) // {"apple":5,"lettuce":7}
// Encode structs res1D := &response1{ Page: 1, Fruits: []string{"apple", "peach", "pear"}, } res1B, _ := json.Marshal(res1D) fmt.Println(string(res1B))
// Custom field names with tags res2D := &response2{ Page: 1, Fruits: []string{"apple", "peach", "pear"}, } res2B, _ := json.Marshal(res2D) fmt.Println(string(res2B))
// Decode JSON into interface{} byt := []byte(`{"num":6.13,"strs":["a","b"]}`) var dat map[string]interface{} if err := json.Unmarshal(byt, &dat); err != nil { panic(err) } fmt.Println(dat)
// Access decoded values num := dat["num"].(float64) fmt.Println(num)
// Decode into struct str := `{"page": 1, "fruits": ["apple", "peach"]}` res := response2{} json.Unmarshal([]byte(str), &res) fmt.Println(res) fmt.Println(res.Fruits[0])
// Stream encoding to stdout enc := json.NewEncoder(os.Stdout) d := map[string]int{"apple": 5, "lettuce": 7} enc.Encode(d)}Key Concepts:
json.Marshal(): Encode Go values to JSONjson.Unmarshal(): Decode JSON to Go values- Exported fields only: Fields must start with capital letters
- Struct tags: Customize JSON field names with
json:"fieldname" - Streaming:
json.NewEncoder(): Stream encoding toio.Writerjson.NewDecoder(): Stream decoding fromio.Reader
- Use streaming for HTTP responses and large data
Additional Essential Topics
Section titled “Additional Essential Topics”Quick Reference
Section titled “Quick Reference”Here are other important topics from Go by Example:
Control Flow:
- For loops (only loop construct in Go)
- If/Else
- Switch statements
- Range (iterate over collections)
Functions:
- Multiple return values
- Variadic functions
- Closures
- Recursion
Data Structures:
- Arrays (fixed size)
- Slices (dynamic)
- Maps (hash tables)
Pointers & Methods:
- Pointers
- Methods on structs
- Interfaces
- Struct embedding
Advanced Concurrency:
- Channel directions (send-only, receive-only)
- Timeouts
- Non-blocking operations
- Closing channels
- Timers and tickers
- Rate limiting
- Atomic counters
- Mutexes
String & Text Processing:
- String functions
- String formatting
- Text templates
- Regular expressions
Data Formats:
- XML encoding/decoding
- Base64 encoding
- URL parsing
Time & Random:
- Time operations
- Epoch time
- Time formatting/parsing
- Random numbers
File System:
- Line filters
- File paths
- Temporary files
- Embed directive
Command Line:
- Command-line arguments
- Command-line flags
- Subcommands
- Environment variables
Testing & Logging:
- Testing and benchmarking
- Logging
Network:
- TCP server
- Signals
Process Management:
- Spawning processes
- Executing processes
- Exit codes
Next Steps
Section titled “Next Steps”- Practice: Work through examples at gobyexample.com
- Interactive Tour: Visit go.dev/tour for hands-on exercises
- Documentation: Read pkg.go.dev for package documentation
- Effective Go: Study go.dev/doc/effective_go
- Build Projects: Create real applications using these concepts
Quick Tips
Section titled “Quick Tips”- Error handling: Always check errors, don’t ignore them
- Defer: Use immediately after acquiring resources
- Goroutines: Lightweight, but always synchronize properly
- Channels: Prefer communication over shared memory
- Interfaces: Small interfaces are better (1-3 methods)
- Formatting: Use
go fmtto format code - Testing: Write tests with
_test.gofiles - Modules: Use Go modules for dependency management
Content compiled from Go by Example and A Tour of Go