Recently I wrote a post on using defers, this is a deeper dive into the defer mechanism.
As previously explained the idea of defer
is to give developers a way to ensure that code is run as a function exits, without having to put the code at the end of the function.
The mechanism for this is the subject of this post.
A defer
is a struct, defined in the runtime package, and has, amongst other things, a pointer to another defer.
Each Goroutine
has its own defer linked list, that is, the Goroutine
has a pointer to a defer
, and that defer
has, as has just been mentioned, a pointer to the next defer
.
A function has an uint32 offset calleddeferreturn that is used to determine where the function’s placement in the defer linked list begins. That is if the function’s offset is 0, then the bottom of that goroutine’s linked list is where the defers for that function began to be placed. That function’s portion of the defer list ends where the next function’s list starts, and so on to the end of the list.
The deferreturn function is inserted into the function code by the compiler when a defer
is found by the exit function.
When a function exits, the offset provided by the function determines where in the defer list should be executed from, that is, the deferreturn
starts at the offset position of the defer list for the goroutine, and executes each deferred function in that list to the end. When the next function exits, the defer list from its offset to the end is only that functions (ie. the function exiting owns the defer list from the offset to the end, functions that exit after this function will own from their own offset to this functions offset, which will become the end once this function has exited. Whew what a mouthful!)
With all of this information it should be easy to predict the output of https://play.golang.org/p/zpWVHX5qwsj
package main
import (
"fmt"
)
func main() {
defer func() {
defer func() {
fmt.Println("Level 2")
fmt.Println("Level 3")
}()
fmt.Println("Level 1")
}()
fmt.Println("Hello, playground")
}
When the main
function exits the outer defer
function is executed. When that defer
exits, the inner defer
functions are executed. When the goroutine’s defer list was built, the inner most defer
’s functions were at offset 0, and the outer defer
’s function was at 2.