# Go Basics, Part 3 — Interfaces, Errors, Collections, Packages, and Concurrency This is the last basics lesson. It covers everything else the main course leans on: interfaces (how `http.Handler` and similar types work), proper error handling patterns, slices and maps, how packages/modules/imports actually work, a first look at goroutines (needed for graceful shutdown), and JSON encoding/decoding. ## 1. Interfaces — Go's version of "any type that can do X" An **interface** is a type defined purely by a set of method signatures. Any type that has those methods automatically satisfies the interface — there's no `implements` keyword, no explicit declaration. This is called **structural typing** or "duck typing, but checked at compile time." ```go package main import "fmt" // Any type with a Speak() string method satisfies Speaker - automatically. type Speaker interface { Speak() string } type Dog struct{} func (d Dog) Speak() string { return "Woof!" } type Cat struct{} func (c Cat) Speak() string { return "Meow!" } func announce(s Speaker) { fmt.Println(s.Speak()) } func main() { announce(Dog{}) // Woof! announce(Cat{}) // Meow! } ``` `Dog` and `Cat` never mention `Speaker` anywhere in their code. They just happen to have a method with the right name and signature, which is enough. This is why, in the main course, `*chi.Mux` can be passed directly to `http.Server{Handler: r}` — `http.Handler` is defined (in the standard library) as: ```go type Handler interface { ServeHTTP(ResponseWriter, *Request) } ``` `*chi.Mux` happens to have a `ServeHTTP` method, so it automatically satisfies `http.Handler`, with zero extra code. Same story for our own handlers wrapped via `http.HandlerFunc(...)` — a small built-in adapter type that turns any function shaped `func(w, r)` into something with a `ServeHTTP` method, satisfying the interface. ### `any` (a.k.a. `interface{}`) The empty interface — one with zero required methods — is satisfied by **every** type, since every type trivially has "at least zero" methods. Go has a built-in alias for this: `any` (added in Go 1.18; older code uses the equivalent `interface{}`). ```go func describe(v any) { fmt.Printf("value: %v, type: %T\n", v, v) } describe(42) // value: 42, type: int describe("hello") // value: hello, type: string describe(User{}) // value: {}, type: main.User ``` You'll see `any` used for things like generic JSON response helpers (`map[string]any`) where the value could be a string, a number, a nested object — anything. ### Type assertions If you have a value typed as an interface (or `any`) and need the concrete type back out, use a **type assertion**: ```go var v any = "hello" s := v.(string) // single-value form - PANICS if v isn't actually a string s, ok := v.(string) // two-value form - SAFE: ok is false on mismatch, no panic if !ok { fmt.Println("v was not a string") } ``` **Always prefer the two-value form** unless you're absolutely certain of the type — a failed single-value assertion crashes your program. This shows up in the main course when reading a value back out of a `context.Context` (Lesson 8) — the value is stored as `any`, so you need a type assertion to get a concrete struct back. ## 2. Error handling, properly Go's `error` is just an interface: ```go type error interface { Error() string } ``` Any type with an `Error() string` method IS an error. The standard library gives you two easy ways to create one: ```go import ( "errors" "fmt" ) err1 := errors.New("something went wrong") err2 := fmt.Errorf("failed to process user %d", 42) ``` ### The `if err != nil` pattern ```go func readConfig() (string, error) { // pretend this can fail return "", errors.New("config file not found") } func main() { config, err := readConfig() if err != nil { fmt.Println("error:", err) return // stop here - don't continue using `config`, it's meaningless } fmt.Println("config:", config) } ``` Checking `err != nil` after every call that can fail, and handling it immediately, is the single most repeated pattern in idiomatic Go — and in the entire main course. ### Wrapping errors with `%w` When an error crosses through several layers of your program, it's useful to add context at each layer without losing the original error: ```go func openFile() error { return errors.New("file not found") } func loadConfig() error { if err := openFile(); err != nil { return fmt.Errorf("load config: %w", err) // %w WRAPS, preserving err } return nil } ``` `%w` (as opposed to `%v` or `%s`) specifically **wraps** the original error, meaning code further up the chain can still inspect what the original error actually was, using `errors.Is` or `errors.As`. ### Sentinel errors and `errors.Is` A **sentinel error** is a specific, predefined error value that callers can check for by identity, not by comparing message strings (which is fragile — messages change, causes bugs). ```go var ErrNotFound = errors.New("not found") func findUser(id int) (string, error) { if id != 1 { return "", ErrNotFound } return "Hamid", nil } func main() { _, err := findUser(99) if errors.Is(err, ErrNotFound) { fmt.Println("no such user!") } } ``` `errors.Is` works correctly even if the error was wrapped with `%w` several layers deep — it "unwraps" automatically to check. This exact pattern (`var ErrUserNotFound = errors.New(...)`, then `errors.Is(err, ErrUserNotFound)`) is used throughout the main course's repository layer. ## 3. Slices and maps — Go's core collection types ### Slices — dynamically-sized lists ```go // A slice literal names := []string{"alice", "bob", "carol"} fmt.Println(names[0]) // "alice" - zero-indexed fmt.Println(len(names)) // 3 names = append(names, "dave") // append returns a NEW slice - reassign it! fmt.Println(names) // [alice bob carol dave] // An empty slice, grown later var scores []int scores = append(scores, 10) scores = append(scores, 20) // Looping (seen in Part 1, repeated here for completeness) for i, name := range names { fmt.Println(i, name) } ``` Important: `append` may or may not modify the original underlying array — you should always use the return value (`names = append(names, ...)`), never assume the original variable was updated in place. ### Maps — key/value lookups ```go ages := map[string]int{ "alice": 30, "bob": 25, } fmt.Println(ages["alice"]) // 30 ages["carol"] = 28 // add/update a key delete(ages, "bob") // remove a key // Reading a key that doesn't exist returns the TYPE'S ZERO VALUE, not an // error or nil-equivalent crash: fmt.Println(ages["nobody"]) // 0 (the zero value for int) // The "comma ok" idiom - check whether a key actually exists: age, ok := ages["nobody"] if !ok { fmt.Println("no such key") } // Looping over a map (order is NOT guaranteed - it's randomized each run) for name, age := range ages { fmt.Println(name, age) } ``` You'll see `map[string]any` used constantly in the main course for building ad-hoc JSON responses, e.g. `map[string]any{"id": user.ID, "email": user.Email}`. ## 4. Packages, imports, and modules — how a real project is organized You already saw `package main` in Part 1. Any other folder full of `.go` files declares its own package name (usually matching the folder name), and can be imported by other code. ``` myproject/ ├── go.mod ├── main.go -- package main └── greeter/ └── greeter.go -- package greeter ``` **`greeter/greeter.go`** ```go package greeter func Hello(name string) string { return "Hello, " + name + "!" } ``` **`main.go`** ```go package main import ( "fmt" "myproject/greeter" // import path = module path + folder path ) func main() { fmt.Println(greeter.Hello("Hamid")) } ``` The import path `"myproject/greeter"` is built from the module's name (declared in `go.mod` via `module myproject`) plus the folder path. This is exactly the pattern behind every internal import you'll see in the main course, e.g.: ```go import "git.hamidsoltani.com/hamid/go-simple-api/internal/config" ``` — the module is `git.hamidsoltani.com/hamid/go-simple-api` (declared once, at the top of the project's `go.mod`), and `internal/config` is the folder path to that specific package. ### The special `internal/` folder Any package inside a folder literally named `internal/` can ONLY be imported by code within the same module (specifically, code rooted at the parent of `internal/`). This is a compiler-enforced way to say "this code is a private implementation detail of this project, not a public library for others to import." The main course's entire codebase lives under `internal/` for exactly this reason. ### External packages and `go.mod` To use code someone else published (like the chi router), you add it as a dependency: ```bash go get github.com/go-chi/chi/v5@latest ``` This downloads the package, records it in `go.mod` (a "require" line with a specific version), and records exact checksums in `go.sum` (so builds are reproducible and verifiably untampered). After that, you import it just like any other package: ```go import "github.com/go-chi/chi/v5" ``` `go mod tidy` is a command you'll run often — it scans your code for imports it doesn't yet know about, fetches them, and also removes `go.mod` entries for anything you've stopped importing. ## 5. A first look at goroutines (needed for Lesson 1's graceful shutdown) A **goroutine** is a lightweight, independently-running function — Go's built-in concurrency primitive. You start one with the `go` keyword: ```go package main import ( "fmt" "time" ) func sayHello() { fmt.Println("hello from a goroutine") } func main() { go sayHello() // starts sayHello running CONCURRENTLY, doesn't block fmt.Println("this may print before OR after 'hello from a goroutine'") time.Sleep(100 * time.Millisecond) // give the goroutine time to run // without this Sleep, main() might exit before sayHello ever runs - // when main() returns, the WHOLE PROGRAM exits immediately, goroutines // and all. } ``` The key thing to understand: `go someFunction()` starts `someFunction` running in the background and immediately continues to the next line — it does **not** wait for `someFunction` to finish. This is exactly why the main course wraps `srv.ListenAndServe()` in a goroutine in Lesson 1: that call blocks forever (serving requests) — running it as a goroutine frees up `main()`'s main line of execution to move on and listen for shutdown signals (Ctrl+C) instead of getting stuck forever inside `ListenAndServe`. We won't go deeper into concurrency (channels, `sync.WaitGroup`, etc.) in this course — the main project only needs this one goroutine pattern. ## 6. JSON basics with `encoding/json` Go's standard library can convert between Go values and JSON text automatically, using struct tags (from Part 2) to control field naming. ### Encoding (Go value → JSON) ```go package main import ( "encoding/json" "fmt" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { u := User{Name: "Hamid", Age: 31} // Marshal converts a Go value into a []byte of JSON text data, err := json.Marshal(u) if err != nil { fmt.Println("error:", err) return } fmt.Println(string(data)) // {"name":"Hamid","age":31} } ``` ### Decoding (JSON → Go value) ```go jsonText := `{"name":"Sara","age":28}` var u User err := json.Unmarshal([]byte(jsonText), &u) // note the & - Unmarshal WRITES into u if err != nil { fmt.Println("error:", err) return } fmt.Println(u.Name, u.Age) // Sara 28 ``` Note the `&u` — just like `rows.Scan(&x)` from database code, `Unmarshal` needs to *write into* your variable, so it needs its address. ### Streaming versions: `Encoder`/`Decoder` When working with HTTP requests/responses (which are streams, not in-memory byte slices), you'll more often see the streaming forms: ```go // Writing JSON directly to an io.Writer (e.g. http.ResponseWriter) json.NewEncoder(w).Encode(u) // Reading JSON directly from an io.Reader (e.g. an HTTP request body) var u User json.NewDecoder(r.Body).Decode(&u) ``` These do the same job as `Marshal`/`Unmarshal` but write/read directly to a stream instead of requiring a full `[]byte` up front. You'll use `NewDecoder(r.Body).Decode(...)` and `NewEncoder(w).Encode(...)` on nearly every handler in the main course, starting in Lesson 1. ## 7. You're ready That's everything the main course leans on. A quick self-check — if these all feel familiar, you're ready for Lesson 1: - Declaring variables with `:=` and `var`, and Go's zero values - Writing functions with multiple return values, and the `if err != nil` pattern - Structs, exported vs. unexported fields, struct tags - Pointers: `&` to get an address, `*` to dereference, and why functions take `*User` instead of `User` when they need to modify it - Methods with value vs. pointer receivers - Interfaces being satisfied implicitly (no `implements` keyword) - Slices (`append`, indexing, `range`) and maps (`map[string]any`, the comma-ok idiom) - How packages/imports/modules fit together, and what `internal/` means - `go someFunc()` starting a goroutine, and why that matters for a blocking call like `ListenAndServe` - `json.NewEncoder(w).Encode(...)` / `json.NewDecoder(r.Body).Decode(&x)` Head to `lesson-01-project-skeleton-chi-routing.md` next.