Câu lệnh defer là cách tiện lợi để chạy một đoạn code trước khi hàm trả về. Defer luôn được kích hoạt ở cuối hàm, thậm trí kể cả những đoạn code không được kiểm soát lỗi và là nguyên nhân gây chương trình bị tạm dừng, thì nó vẫn đảm bảo rằng Defer code được chạy. Định nghĩa này nghe có vẻ phức tạp nhưng thực tế nó tương đối dễ hiểu thông qua ví dụ
package main import ( "fmt" ) func finished() { fmt.Println("Finished finding largest") } func largest(nums []int) { defer finished() fmt.Println("Started finding largest") max := nums[0] for _, v := range nums { if v > max { max = v } } fmt.Println("Largest number in", nums, "is", max) } func main() { nums := []int{78, 109, 2, 563, 300} largest(nums) }
Kết quả
Started finding largest Largest number in [78 109 2 563 300] is 563 Finished finding largest
Ví dụ bên trên là tìm số lớn nhất của slice truyền vào. Hàm largest
nhận int slice như một tham số truyền vào rồi in ra số lớn nhất. Hàng đầu tiên của hàm có câu lệnh defer finished()
, nghĩa là hàm finished()
sẽ chỉ được gọi khi hàm largest
trả về.
Phương thức
Defer không chỉ giới hạn sử dụng trong hàm, nó hoàn toàn có thể sử dụng cho gọi phương thức. Hãy thử xem ví dụ sau
package main import ( "fmt" ) type person struct { firstName string lastName string } func (p person) fullName() { fmt.Printf("%s %s",p.firstName,p.lastName) } func main() { p := person { firstName: "John", lastName: "Smith", } defer p.fullName() fmt.Printf("Welcome ") }
Kết quả
Welcome John Smith
Trong hàm main()
, chúng ta gọi defer p.fullname()
, nên fullname
của của person
được in ra sau cùng, khi chương trình chạy xong.
Tính toán tham số
Tham số của hàm Defer được tính toán khi câu lệnh defer
được chạy và không phải là khi gọi hàm mới thực hiện tính toán. Hãy cùng xem ví dụ sau
package main import ( "fmt" ) func printA(a int) { fmt.Println("value of a in deferred function", a) } func main() { a := 5 defer printA(a) a = 10 fmt.Println("value of a before deferred function call", a) }
Biến a
được khởi tạo với giá trị bằng 5
. Khi câu lệnh defer được chạy, giá trị của a
là 5
và vì thế đây sẽ là tham số được truyền vào hàm printA
. Chúng ta cũng đã thay đổi giá trị của a nằm dưới defer, hàng tiếp theo phía sau printA
.
Kết quả
value of a before deferred function call 10 value of a in deferred function 5
Stack defer
Khi một hàm gọi nhiều defer, chúng sẽ được thêm vào stack và được gọi theo thứ tự Last In First Out (LIFO)
Chúng ta sẽ viết một ví dụ dùng để in ra một chuỗi đảo ngược sử dụng stack
package main import ( "fmt" ) func main() { name := "Naveen" fmt.Printf("Original String: %s\n", string(name)) fmt.Printf("Reversed String: ") for _, v := range []rune(name) { defer fmt.Printf("%c", v) } }
Trong đoạn code bên trên, vòng lặp for range
thực hiện lặp một chuỗi và gọi defer fmt.Printf("%c", v)
. Các defer này sẽ được thêm vào stack và gọi theo thứ tự LIFO và do đó chuỗi được in ra theo thứ tự đảo ngược
Kết quả
Original String: Naveen Reversed String: neevaN
Panic
Hãy cùng xem ví dụ sau về code Panic
package main import "fmt" func main() { deferExample() fmt.Println("Returned from deferExample.") deferPanicExample() fmt.Println("Returned from deferPanicExample.") } func deferExample() { defer fmt.Println("Deferred log.") fmt.Println("Print line.") } func deferPanicExample() { defer func() { if r := recover(); r != nil { fmt.Println("Recover from panic", r) } }() fmt.Println("Call recurse.") recursePanic(0) fmt.Println("Return normally from recurse.") } func recursePanic(count int) { if count > 3 { fmt.Println("Panicking!") panic(fmt.Sprintf("%v", count)) } defer fmt.Println("Defer log in recurse", count) fmt.Println("Print line in recurse", count) recursePanic(count + 1) }
Code bên trên sử dụng đệ quy và chúng ta sẽ có một panic khi count
lớn hơn 3. fmt.Sprintf
dùng để định dạng một string dựa trên giá trị. Điều này là bắt buộc vì tham số của hàm panic
yêu cầu một string.
Ngoài ra hàm recover()
có phép chúng ta lấy lại từ panic xuống stack được gọi. recover
này gọi đến hàm defer anonymous, chúng ta cần đảm bảo rằng nó sẽ luôn luôn được gọi khi gọi deferPanicExample
.
Kết quả
Print line. Deferred log. Returned from deferExample. Call recurse. Print line in recurse 0 Print line in recurse 1 Print line in recurse 2 Print line in recurse 3 Panicking! Defer log in recurse 3 Defer log in recurse 2 Defer log in recurse 1 Defer log in recurse 0 Recover from panic 4 Returned from deferPanicExample.