Interfaces, Goroutines & Channels
Interfaces
// Interface — implicit implementation (no "implements")
type Animal interface {
Sound() string
Name() string
}
type Dog struct{ name string }
func (d Dog) Sound() string { return "Woof" }
func (d Dog) Name() string { return d.name }
type Cat struct{ name string }
func (c Cat) Sound() string { return "Meow" }
func (c Cat) Name() string { return c.name }
func describe(a Animal) {
fmt.Printf("%s says %s
", a.Name(), a.Sound())
}
// Works for any type implementing Animal
describe(Dog{"Rex"})
describe(Cat{"Whiskers"})
// Common interfaces (stdlib)
// io.Reader — Read(p []byte) (n int, err error)
// io.Writer — Write(p []byte) (n int, err error)
// fmt.Stringer — String() string
// error — Error() string
// Empty interface — any type
var any interface{} = 42
any = "hello"
any = Dog{}
// Type assertion
s, ok := any.(string)
if !ok { ... }
// Type switch
switch v := any.(type) {
case int:
fmt.Printf("int: %d
", v)
case string:
fmt.Printf("string: %s
", v)
default:
fmt.Printf("other: %T
", v)
}Error Handling
// Idiomatic Go: explicit error returns
func fetchUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("invalid id: %d", id)
}
// ...
return &User{ID: id}, nil
}
// Check errors immediately
user, err := fetchUser(5)
if err != nil {
return fmt.Errorf("fetchUser failed: %w", err) // wrap with context
}
// Custom error type
type NotFoundError struct {
Resource string
ID int
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with id %d not found", e.Resource, e.ID)
}
// Unwrap errors
var nfe *NotFoundError
if errors.As(err, &nfe) {
fmt.Println("not found:", nfe.Resource)
}
if errors.Is(err, ErrTimeout) { ... } // compare sentinel errors
// Panic/recover (only for truly unexpected failures)
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()Goroutines & Channels
// Goroutine — lightweight thread, launched with 'go'
go func() {
fmt.Println("running concurrently")
}()
// Channel — typed pipe for communication between goroutines
ch := make(chan int) // unbuffered — blocks until both sides ready
bch := make(chan int, 10) // buffered — blocks when full
// Send / receive
go func() { ch <- 42 }()
val := <-ch // receive (blocks)
// Range over channel
go func() {
for _, v := range []int{1, 2, 3} { ch <- v }
close(ch)
}()
for v := range ch { fmt.Println(v) } // until closed
// Select — wait on multiple channels
select {
case msg := <-ch1:
fmt.Println("from ch1:", msg)
case msg := <-ch2:
fmt.Println("from ch2:", msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
default:
fmt.Println("no message ready")
}
// WaitGroup — wait for goroutines to finish
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
fetch(u)
}(url)
}
wg.Wait()
// Mutex — protect shared state
var mu sync.Mutex
mu.Lock()
sharedMap[key] = value
mu.Unlock()
// sync.Once — run exactly once
var once sync.Once
once.Do(func() { initExpensiveResource() })Context
import "context"
// Pass context for cancellation/timeout
func fetchUser(ctx context.Context, id int) (*User, error) {
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// ...
}
// Timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
user, err := fetchUser(ctx, 1)
// Cancellation
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
time.Sleep(2 * time.Second)
cancel() // signal worker to stop
// Check cancellation in worker
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("cancelled:", ctx.Err())
return
default:
doWork()
}
}
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free