Skip to content

Functions & Defer

Go functions return multiple values — đây là signature feature giúp error handling trở nên tự nhiên. Kết hợp với defer, bạn có một powerful toolkit cho resource management.

📤 Multiple Return Values

Go cho phép return nhiều giá trị từ một function — pattern cốt lõi cho error handling:

go
// Pattern chuẩn: (result, error)
func GetUser(id int64) (*User, error) {
    user, err := db.QueryUser(id)
    if err != nil {
        return nil, fmt.Errorf("get user %d: %w", id, err)
    }
    return user, nil
}

// Caller
user, err := GetUser(42)
if err != nil {
    log.Error("failed to get user", "error", err)
    return
}
// Sử dụng user...

Multiple Values Use Cases

go
// 1. Value + OK pattern (map lookup, type assertion)
value, ok := myMap["key"]
if !ok {
    // key không tồn tại
}

// 2. Quotient + Remainder
func divmod(a, b int) (int, int) {
    return a / b, a % b
}
q, r := divmod(17, 5)  // q=3, r=2

// 3. Min + Max in one pass
func minMax(nums []int) (min, max int) {
    // ... implementation
    return
}

📌 HPN Standard: Error Handling

Quy tắc vàng: Xử lý error NGAY SAU khi gọi function.

go
// ✅ Correct: Handle immediately
result, err := doSomething()
if err != nil {
    return err
}
// Tiếp tục với result...

// ❌ Wrong: Defer error handling
result1, err1 := step1()
result2, err2 := step2()
result3, err3 := step3()
if err1 != nil || err2 != nil || err3 != nil {
    // Quá muộn! Bạn đã mất context
}

🏷️ Named Return Values

Go cho phép đặt tên cho return values:

go
func parseConfig(path string) (config *Config, err error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return  // "naked return" - trả về zero values
    }
    
    config = &Config{}
    err = json.Unmarshal(data, config)
    return  // Trả về config và err hiện tại
}

Pros vs Cons

Pros ✅Cons ❌
Self-documenting: Tên biến giải thích meaningShadowing risk: Dễ shadow bằng :=
Naked returns: Code ngắn hơn cho simple functionsReadability giảm: Không rõ return cái gì
Defer access: Có thể modify trong deferImplicit behavior: Magic không rõ ràng

🔥 Raizo's Pitfall: Shadowing Trap

go
func badFunc() (result int, err error) {
    if condition {
        result, err := compute()  // ⚠️ SHADOW! Tạo biến MỚI
        if err != nil {
            return  // Trả về named 'err' (nil), không phải inner err!
        }
        // result ở đây là inner result, không phải named return
    }
    return  // result = 0, err = nil (không đúng!)
}

// ✅ Fix: Dùng = thay vì :=
func goodFunc() (result int, err error) {
    if condition {
        result, err = compute()  // Assign to named returns
        if err != nil {
            return
        }
    }
    return
}

📌 HPN Standard: Named Returns

  • ✅ Dùng cho short functions (<15 lines)
  • ✅ Dùng khi cần modify return value trong defer
  • ❌ Tránh naked returns trong long functions
  • ❌ Không dùng nếu có multiple exit points phức tạp

⏱️ Defer: Resource Management

defer hoãn execution của function call cho đến khi enclosing function return.

go
func processFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()  // Chắc chắn sẽ đóng file!
    
    // Process file...
    // Dù có return ở đâu, file.Close() vẫn được gọi
    return nil
}

LIFO (Last-In-First-Out) Principle

🎓 Professor Tom's Deep Dive: Defer Stack

Multiple defers được thực thi theo thứ tự LIFO — như một stack:

go
func main() {
    defer fmt.Println("1st defer")
    defer fmt.Println("2nd defer")
    defer fmt.Println("3rd defer")
    fmt.Println("Main body")
}

// Output:
// Main body
// 3rd defer  ← Last in, first out
// 2nd defer
// 1st defer

Visualization:

┌─────────────────────────────────────┐
│           DEFER STACK               │
├─────────────────────────────────────┤
│  TOP → fmt.Println("3rd defer")     │ ← Executed first
│        fmt.Println("2nd defer")     │
│        fmt.Println("1st defer")     │ ← Executed last
└─────────────────────────────────────┘

Common Use Cases

go
// 1. File handling
func readConfig() error {
    f, err := os.Open("config.json")
    if err != nil {
        return err
    }
    defer f.Close()
    // ...
}

// 2. Mutex unlock
func (s *Service) UpdateCounter() {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.counter++
}

// 3. Timing/Tracing
func processRequest(req *Request) {
    defer trace.StartSpan("processRequest").End()
    // ...
}

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

Defer với Named Returns (Advanced)

go
func writeFile(path string, data []byte) (err error) {
    f, err := os.Create(path)
    if err != nil {
        return
    }
    
    defer func() {
        closeErr := f.Close()
        if err == nil {  // Chỉ update nếu chưa có error
            err = closeErr
        }
    }()
    
    _, err = f.Write(data)
    return
}

🎓 Under the Hood: Defer Performance

Go 1.14+: Defer overhead gần như eliminated cho simple cases (~0 ns).

Trước Go 1.14: Mỗi defer tốn ~35ns do heap allocation.

go
// Benchmark comparison (pre-1.14 vs post-1.14)
// BenchmarkWithDefer-8     50000000    35.2 ns/op (Go 1.13)
// BenchmarkWithDefer-8     500000000    2.1 ns/op (Go 1.14+)

// Trong hot paths cực kỳ performance-critical, 
// vẫn có thể dùng explicit cleanup thay vì defer

Kết luận: Với Go hiện đại, always use defer cho resource cleanup. Overhead là negligible.


🔄 Closures: Functions Capturing Variables

Closure là function captures variables từ enclosing scope:

go
func counter() func() int {
    count := 0
    return func() int {
        count++  // Capture biến 'count' từ outer function
        return count
    }
}

func main() {
    c := counter()
    fmt.Println(c())  // 1
    fmt.Println(c())  // 2
    fmt.Println(c())  // 3
    
    c2 := counter()   // New closure, new 'count'
    fmt.Println(c2()) // 1
}

Closure Capture Mechanism

🎓 Professor Tom's Deep Dive: How Capture Works

Khi closure capture một variable, nó giữ reference đến variable đó, không phải copy:

go
func createFunctions() []func() {
    var funcs []func()
    
    for i := 0; i < 3; i++ {
        funcs = append(funcs, func() {
            fmt.Println(i)  // Capture reference to 'i'
        })
    }
    return funcs
}

func main() {
    for _, f := range createFunctions() {
        f()
    }
}
// Output (Go < 1.22): 3, 3, 3 (tất cả reference cùng 'i')
// Output (Go 1.22+): 0, 1, 2 (mỗi iteration có 'i' riêng)

Memory Layout:

┌─────────────────────────────────────────────┐
│              CLOSURE OBJECT                 │
├─────────────────────────────────────────────┤
│  func ptr  │  captured vars (by reference) │
│  ────────  │  ─────────────────────────────│
│  0x4a2b... │  &count → [actual int value]  │
└─────────────────────────────────────────────┘

Practical Closure Patterns

go
// 1. Middleware factory
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s took %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// 2. Functional options pattern
type ServerOption func(*Server)

func WithPort(port int) ServerOption {
    return func(s *Server) {
        s.port = port  // Capture 'port'
    }
}

// 3. Memoization
func memoize(fn func(int) int) func(int) int {
    cache := make(map[int]int)
    return func(n int) int {
        if v, ok := cache[n]; ok {
            return v
        }
        result := fn(n)
        cache[n] = result
        return result
    }
}

📦 Variadic Functions

Variadic functions nhận số lượng arguments không cố định:

go
// Định nghĩa với ...T
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// Gọi function
sum(1, 2, 3)           // 6
sum(1, 2, 3, 4, 5)     // 15
sum()                   // 0

// Truyền slice với ...
numbers := []int{1, 2, 3, 4}
sum(numbers...)         // 10 - "spread" slice

Variadic Rules

go
// 1. Variadic phải là parameter CUỐI CÙNG
func valid(prefix string, nums ...int) {}   // ✅
// func invalid(nums ...int, suffix string) {} // ❌ Compile error

// 2. Có thể không truyền gì (empty slice)
func logMessages(msgs ...string) {
    if len(msgs) == 0 {
        return
    }
    // ...
}

// 3. Underlying type là slice
func printAll(args ...any) {
    fmt.Printf("Type: %T\n", args)  // []interface {}
    for _, arg := range args {
        fmt.Println(arg)
    }
}

Real-world Examples

go
// fmt.Printf - variadic với any
func Printf(format string, a ...any) (n int, err error)

// append - built-in variadic
slice = append(slice, 1, 2, 3)
slice = append(slice, otherSlice...)

// Custom logger
func (l *Logger) Infof(format string, args ...any) {
    l.log(INFO, fmt.Sprintf(format, args...))
}

📊 Summary

ConceptKey Point
Multiple Returns(result, error) pattern cho error handling
Named ReturnsSelf-documenting, nhưng cẩn thận shadowing
DeferLIFO execution, dùng cho resource cleanup
ClosuresCapture variables by reference
Variadic...T cho flexible argument count

➡️ Tiếp theo

Functions nắm vững rồi! Tiếp theo: Slices & Maps Deep Dive - Slice Header, append mechanics, và memory management.