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ồ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
x
và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 đó,
defer
tốn thêm overhead → với code performance-critical (tight loop), người ta có thể tránhdefer
và 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 = 0
Không phải 3 lần in
2
. Vì mỗi lần defer copy giá trị củai
ngay 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 = 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