也許你不知道的 Go 中 defer 的一個隱藏功能

在開始使用 Go 進行編碼時,Defer 是要關注的一個很重要的特性。它非常簡單:在任何函數中,給其他函數的調用加上前綴 defer 以確保該函數在外部函數退出之前立即執行,即使外部函數出現異常被中斷,該延遲函數也將運行。

但是,你還可以使用 defer 在任何函數開始後和結束前執行配對的代碼。這個隱藏的功能在網上的教程和書籍中很少提到。要使用此功能,需要創建一個函數並使它本身返回另一個函數,返回的函數將作為真正的延遲函數。在 defer 語句調用父函數後在其上添加額外的括號來延遲執行返回的子函數如下所示:

<code>func main() {defer greet()()fmt.Println("Some code here...")}func greet() func() {fmt.Println("Hello!")return func() {fmt.Println("Bye!")} // this will be deferred}/<code>

輸出以下內容:

<code>Hello!Some code here...Bye!/<code>

父函數返回的函數將是實際的延遲函數。父函數中的其他代碼將在函數開始時(由 defer 語句放置的位置決定)立即執行。

這為開發者提供了什麼能力?因為在函數內定義的匿名函數可以訪問完整的詞法環境(lexical environment),這意味著在函數中定義的內部函數可以引用該函數的變量。在下一個示例中看到的,參數變量在 measure 函數第一次執行和其延遲執行的子函數內都能訪問到:

<code>func main() {example()otherExample()}func example(){defer measure("example")()fmt.Println("Some code here")}func otherExample(){defer measure("otherExample")()fmt.Println("Some other code here")}func measure(name string) func() {start := time.Now()fmt.Printf("Starting function %s\\n", name)return func(){fmt.Printf("Exiting function %s after %s\\n", name, time.Since(start)) }}/<code>

輸出以下內容:

<code>Starting exampleSome code hereExiting example after 0sStarting otherExampleSome other code hereExiting otherExample after 0s/<code>

此外函數命名的返回值也是函數內的局部變量,所以上面例子中的 measure 函數如果接收命名返回值作為參數的話,那麼命名返回值在延遲執行的函數中也能訪問到,這樣就能將 measure 函數改造成記錄入參和返回值的工具函數。

下面的示例是引用《go 語言程序設計》中的代碼段:

<code>func bigSlowOperation() {defer trace("bigSlowOperation")() // don't forget the extra parentheses   // ...lots of work…time.Sleep(10 * time.Second) // simulate slow operation by sleeping}func trace(msg string) func() {start := time.Now()log.Printf("enter %s", msg)return func() {log.Printf("exit %s (%s)", msg,time.Since(start))}}/<code>

可以想象,將代碼延遲在函數的入口和出口使用是非常有用的功能,尤其是在調試代碼的時候。


分享到:


相關文章: