On a hidden function of defer in go

  • 2020-08-22 22:12:02
  • OfStack

Defer is an important feature to focus on when you start coding with Go. It's very simple: In any function, you prefix calls to other functions with defer to ensure that the function executes immediately before the external function exits, and that the deferred function runs even if an exception to the external function is interrupted.

However, you can also use defer to execute pairing code after any function starts and before any function ends. This hidden feature is rarely mentioned in online tutorials and books. To use this feature, you need to create a function and have it itself return another function, which will act as a true delay function. Add extra parentheses to the parent function after the defer statement calls it to delay execution of the returned child function as follows:


func main() {
  defer greet()() 
  fmt.Println("Some code here...")
}

func greet() func() {
  fmt.Println("Hello!")
  return func() { fmt.Println("Bye!") } // this will be deferred
}

Output the following:

[

Hello!
Some code here...
Bye!

]

The function returned by the parent function will be the actual delay function. The rest of the code in the parent function is executed immediately at the beginning of the function, depending on where the defer statement is placed.

What capabilities does this provide to developers? Because anonymous functions defined within a function have access to the full lexical environment (lexical environment), this means that internal functions defined within the function can refer to the function's variables. As you will see in the next example, parameter variables are accessible both within the first execution of the measure function and its delayed subfunctions:


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)) }
}

Output the following:

[

Starting example
Some code here
Exiting example after 0s
Starting otherExample
Some other code here
Exiting otherExample after 0s

]

In addition, the return value of the named function is also a local variable within the function, so the measure function in the above example receives the named return value as a parameter, then the named return value is accessed in the delayed function, thus converting the measure function into a utility function that records the parameters and return values.

The following example refers to a code snippet from go Language Programming:


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)) 
  }
}

As you can imagine, deferring the use of code at the entry and exit of functions is a very useful feature, especially when debugging code.


Related articles: