Difference between Value and Pointer variable in Defer in Go
Table of Contents
defer
is a useful function to do cleanup, as it will execute in LIFO order before the surrounding function returns. If you don’t know how it works, sometimes the execution result may confuse you.
How it Works and Why Value or Pointer Receiver Matters #
I found an interesting code on Stack Overflow:
type X struct {
S string
}
func (x X) Close() {
fmt.Println("Value-Closing", x.S)
}
func (x *X) CloseP() {
fmt.Println("Pointer-Closing", x.S)
}
func main() {
x := X{"Value-X First"}
defer x.Close()
x = X{"Value-X Second"}
defer x.Close()
x2 := X{"Value-X2 First"}
defer x2.CloseP()
x2 = X{"Value-X2 Second"}
defer x2.CloseP()
xp := &X{"Pointer-X First"}
defer xp.Close()
xp = &X{"Pointer-X Second"}
defer xp.Close()
xp2 := &X{"Pointer-X2 First"}
defer xp2.CloseP()
xp2 = &X{"Pointer-X2 Second"}
defer xp2.CloseP()
}
The output is:
|
|
Take a look at line 5-6, why Pointer-Closing Value-X2 Second
was printed twice? According to Effective Go, “The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes.”. And the function’s parameters will saved anew when evaluated.
As x2
is value and the defer function CloseP
’s receiver is a pointer, once defer
executes, it will create a pointer which points to x2
as function’s caller. In the following defer, it will create a pointer which point to x2
again. Although x2.S
change to “Second”, x2
’s address never changes. Finally, when these two defer is called, the same log was printed again.
How to Exit Program and Run all Defer #
From Golang Runtime:
runtime.Goexit()
terminates the goroutine that calls it. No other goroutine is affected. Goexit runs all deferred calls before terminating the goroutine. Because Goexit is not a panic, any recover calls in those deferred functions will return nil.Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes.
If you want the program to exit normally, just add defer os.Exit(0)
at the top of main
function. Here is the example code:
package main
import (
"fmt"
"os"
"runtime"
"time"
)
func subGoroutine() {
defer fmt.Println("exit sub routine")
for {
fmt.Println("sub goroutine running")
time.Sleep(1 * time.Second)
}
}
func main() {
defer os.Exit(0)
defer fmt.Println("calling os.Exit")
go subGoroutine()
time.Sleep(2 * time.Second)
runtime.Goexit()
}
Output:
sub goroutine running
sub goroutine running
sub goroutine running
calling os.Exit
Process finished with exit code 0
The defer code in main goroutine are executed, but those in subGoroutine
will not be executed. As os.Exit
will
Exit causes the current program to exit with the given status code. Conventionally, code zero indicates success, non-zero an error. The program terminates immediately; deferred functions are not run.
from godoc