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 defertheo 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 mainreturn, stack được pop:2nd deferchạy trước, rồi1st 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à:
- 
Gán xvào giá trị trả về (return value).
- 
Chạy các defer.
- 
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 đó, defertốn thêm overhead → với code performance-critical (tight loop), người ta có thể tránhdefervà gọi thủ công (nhưng giờ thường không cần).
8. Các bẫy phổ biến
- 
Đố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 = 0Không phải 3 lần in 2. Vì mỗi lần defer copy giá trị củaingay lúc gọi.
- 
Capture biến reference trong closure: for i := 0; i < 3; i++ { defer func() { fmt.Println("i =", i) }() }Output: i = 3 i = 3 i = 3Vì closure tham chiếu biến i, đến lúc defer chạy,i=3.
9. Best practices
- 
Luôn dùng defercho cleanup: file, mutex.Unlock, db.Close, http.Body.Close.
- 
Không ngại dùng defertrong 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
 
 