## Go: Defer & Anonymous function / Closure ### Defer 推遲 關鍵字`defer`允許我們延遲到函式 return 的前一刻才執行某個語句或函式,無論函式正常的 return 或出現錯誤。它經常被用於關閉檔案、結束資料庫連接以及解鎖資源。 ```go= package main import "fmt" func main() { function1() } func function1() { fmt.Printf("In function1 at the top\n") defer function2() fmt.Printf("In function1 at the bottom!\n") } func function2() { fmt.Printf("Function2: Deferred until the end of the calling function!") } ``` ``` OUTPUT: In Function1 at the top In Function1 at the bottom! Function2: Deferred until the end of the calling function! ``` #### 重點特性 - 如果有多個 defer 表達式,呼叫順序為==LIFO==,越後面的 defer 表達式越先被呼叫。 ```go= func main() { for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } } ``` ``` OUTPUT: 4 3 2 1 0 ``` - defer 的函式所需的參數會立即求值,並以pass by value的方式傳入函式,但延後執行實際的 function call ```go= func trace(s string) string { fmt.Println("entering:", s) return s } func un(s string) { fmt.Println("leaving:", s) } func a() { defer un(trace("a")) // 立即執行 trace("a"),並將return的"a"傳入un() fmt.Println("in a") // 離開 a(),執行 un("a"),印出 "leaving: a" } func b() { defer un(trace("b")) // 立即執行 trace("b"),並將return的"b"傳入un() fmt.Println("in b") a() // 呼叫 a() // 離開 b(),執行 un("b"),印出 "leaving: b" } func main() { b() } ``` ``` OUTPUT: entering: b in b entering: a in a leaving: a leaving: b ``` #### 具名回傳值 named return values 在函式宣告時就直接對return值命名,例如以下程式的result,且此命名會被視為**已宣告**在函式內的變數,在整個函式內都有效。 ```go= func foo() (result int) { defer func() { result++ }() return 1 } ``` 此段函式的return是 2,並非是 1 !!! 為什麼呢? 當使用具名回傳值時,實際是回傳 result 這個變數本身的值,而函式執行到 return 1 的時候,會把 1 賦值到 result 變數,接著再執行defer函式,最後才真的 return result 結束 foo 這個函式。 因此可以將上面的函式看成以下這段 ```go= func foo() (result int) { result = 1 func() { result++ }() return } ``` 再次強調要特別注意當使用了defer就不能把return xxx這行視為atomic operation,實際步驟的拆解是: 1. 先給 return value 賦值 2. 呼叫 defer 函式 3. 最後回到呼叫函式(caller)中 如果到目前都能理解的話,應該可以輕鬆的看出下面程式的輸出 ```go= package main import "fmt" func f() (r int) { defer func(r int) { r-- fmt.Println(r) }(r) return 1 } func main() { fmt.Println(f()) } ``` :::spoiler OUTPUT:(點開看答案) -1 1 ::: #### 不具名回傳值 以下函式執行 return r 時,是將變數 r 的值(也就是 1)複製一份給 return ,並且最後回傳的是這個預先複製的值,而不是變數當下的值,所以函式 f() 回傳的是 1 ```go= func f() int { r := 1 defer func() { r++ }() return r } ``` 執行步驟: 1. 宣告變數 r = 1 2. 執行 return r:將 r 的值複製為 return 的值 (pass by value) 3. defer 函式執行 r++ 4. 雖然 r++ 執行了,但它改的是變數,而回傳值早就複製完了 ### Anonymous function 匿名函式 當我們不希望給函式取名字時,可以使用匿名函式來達成,這樣的一個函式不能夠獨立存在,但可以被賦值於某個變數,即將函式的位址保存到變數中,接著透過變數名稱對函式進行呼叫,當然也可以直接對匿名函式進行呼叫。 下面的程式展示如何將匿名函式賦值給變數並對其進行呼叫: ```go= package main import "fmt" func main() { foo() } func foo() { for i := 0; i < 3; i++ { g := func(i int) { fmt.Printf("%d ", i) } g(i) //賦值到變數g 用g()的方式去呼叫 fmt.Printf(" - g is of type %T and has value %v\n", g, g) } } ``` ``` OUTPUT: 0 - g is of type func(int) and has value 0x690ec0 1 - g is of type func(int) and has value 0x690ec0 2 - g is of type func(int) and has value 0x690ec0 ``` 我們可以看到變數 g 代表的是 func(int) ,變數的值是一個記憶體位址,所以我們實際上擁有的是一個函式值。 ### Closure 閉包 閉包本質上就是一個匿名函式,是一種函式值(function value),它可以**捕捉並包裝**其外部作用域中的變數。 我們可以說一個閉包繼承了函式所宣告時的作用域,這個作用域內的變數都被共享到閉包的環境中,因此這些變數可以在閉包中被操作。 而且即使函式結束了,被閉包包裝進去的變數依然持續存在,所以經常被使用於封裝臨時的狀態或變數。 以下程式中 adder 回傳一個匿名函式或是稱為閉包: ```go= func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { bar := adder() fmt.Println(bar(1)) fmt.Println(bar(2)) fmt.Println(bar(3)) } ``` 也可以把 sum 寫進 adder 的參數: ```go= func adder(sum int) func(int) int { return func(x int) int { sum += x return sum } } func main() { bar := adder(0) fmt.Println(bar(1)) fmt.Println(bar(2)) fmt.Println(bar(3)) } ``` ``` OUTPUT: 1 3 6 ``` adder 內的變數 sum 用 pass by reference 的方式傳入閉包,即使 adder 函式已經被返回了 sum 也不會馬上被銷毀,因為閉包仍然持續指向它的參考(reference),所以每次呼叫 adder 函式 sum 都會累加。其效果有點類似 C 語言的 static variable >簡單註釋: 變數被用 pass by reference 傳入閉包,稱為「escape to heap」,變數由stack被分配至heap中,之後的記憶體管理由GO的 garbage collector 負責。 --- Reference: https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/06.4.md https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.4.html https://openhome.cc/Gossip/Go/Closure.html