0

Golang: Defer

1. Bản chất của defer

  • Khi bạn gọi defer someFunc(args...), Go runtime lưu lại lệnh gọi vào một stack đặc biệt (defer stack) gắn với frame của hàm hiện tại.

  • Khi hàm return, runtime sẽ lần lượt pop và thực thi các lời gọi defer theo thứ tự ngược lại (LIFO).

  • Các đối số của defer được đánh giá ngay tại thời điểm defer, không phải lúc defer thực thi.

👉 Nói cách khác: defer hoạt động như một “finally” block tự động, nhưng gọn hơn và mạnh hơn.


2. Ví dụ cơ bản

`package main import "fmt" func main() { fmt.Println("Start") defer fmt.Println("1st defer") defer fmt.Println("2nd defer")

fmt.Println("End")

}`

Kết quả:

Start End 2nd defer 1st defer

📌 Giải thích:

  • defer fmt.Println("1st defer") được push vào stack trước.

  • defer fmt.Println("2nd defer") push vào sau.

  • Khi main return, stack được pop: 2nd defer chạy trước, rồi 1st defer.


3. Đối số được đánh giá ngay

func demo() { x := 10 defer fmt.Println("defer x =", x) x = 20 fmt.Println("now x =", x) }

Kết quả:

now x = 20 defer x = 10

📌 Vì x được copy làm đối số tại thời điểm defer, nên dù biến x đổi thành 20, defer vẫn in ra 10.


4. Dùng với cleanup (use case chuẩn)

f, err := os.Open("data.txt") if err != nil { return } defer f.Close() // đảm bảo đóng file, kể cả return sớm data := make([]byte, 100) f.Read(data)

👉 Giúp code gọn gàng và an toàn, không quên đóng tài nguyên.


5. Thứ tự với return

func test() int { x := 5 defer func() { fmt.Println("defer, x =", x) }() x = 10 return x }

Kết quả:

defer, x = 10

📌 Lưu ý: return x trong Go thực ra là:

  1. Gán x vào giá trị trả về (return value).

  2. Chạy các defer.

  3. Thực sự return.

Do đó defer thấy x=10.


6. Dùng defer để thay đổi return value

Go cho phép named return values. Trong trường hợp này, defer có thể ghi đè kết quả trả về:

func test() (result int) { defer func() { result += 1 // thay đổi giá trị trả về }() return 41 } func main() { fmt.Println(test()) // 42 }


7. Chi phí của defer

  • Trong Go 1.14+, defer đã được tối ưu hoá (almost free trong hot path).

  • Trước đó, defer tốn thêm overhead → với code performance-critical (tight loop), người ta có thể tránh defer và gọi thủ công (nhưng giờ thường không cần).


8. Các bẫy phổ biến

  1. Đối số được copy sớm:

    for i := 0; i < 3; i++ { defer fmt.Println("i =", i) }

    Output (khi return main):

    i = 2 i = 1 i = 0

    Không phải 3 lần in 2. Vì mỗi lần defer copy giá trị của i ngay lúc gọi.

  2. Capture biến reference trong closure:

    for i := 0; i < 3; i++ { defer func() { fmt.Println("i =", i) }() }

    Output:

    i = 3 i = 3 i = 3

    Vì closure tham chiếu biến i, đến lúc defer chạy, i=3.


9. Best practices

  • Luôn dùng defer cho cleanup: file, mutex.Unlock, db.Close, http.Body.Close.

  • Không ngại dùng defer trong loop ở Go 1.14+ (đã tối ưu).

  • Hiểu rõ thời điểm evaluation của arguments để tránh bug subtle.


👉 Tóm lại, defer = một cơ chế ngôn ngữ đơn giản mà cực mạnh:

  • An toàn tài nguyên.

  • Đảm bảo “cleanup” kể cả panic/return sớm.

  • Có thể “hack” return value với named result.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí