Golang provides the defer keyword to perform cleanup before a function exits. We won’t go into the basics of how to use it. Here’s a summary of some things to keep in mind.

defer execution order

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func TestFunc() {
    defer func() {
        fmt.Println("A")
    }()

    defer func() {
        fmt.Println("B")
    }()

    defer func() {
        fmt.Println("C")
    }()
}

func main() {
    TestFunc()
}

Running results

1
2
3
C
B
A

That is, the order of execution of defer is the reverse of the order of code, which is last-in-first-out.

defer and return order of execution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func deferFunc() int {
    fmt.Println("defer func running")
    return 0
}

func returnFunc() int {
    fmt.Println("return func running")
    return 0
}

func TestFunc() int {
    defer deferFunc()
    return returnFunc()
}

func main() {
    TestFunc()
}

Running results

1
2
return func running
defer func running

You can see that the return statement is executed first and the defer function is executed later.

defer affects the return value

Since the function executed by defer is executed after the return statement. Can defer change the return value of a function? Consider the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

func TestFunc() (result int) {
    defer func() {
        result = 1024
    }()

    return 2048

}

func main() {
    fmt.Println(TestFunc())
}

The result is 1024. return is 2048, i.e. defer can indeed change the return value of a function. Look at a similar example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

func TestFunc() int {
    result := 1024

    defer func() {
        result = 2048
    }()

    return result
}

func main() {
    fmt.Println(TestFunc())
}

The result is 1024, where the defer func updates the result value but has no effect on the return value of the function. The reason for this is that the function’s return value of type int is unnamed (i.e. anonymous), so defer func cannot modify that return value, only the result variable.

defer function pre-calculates argument values

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
	"time"
)

func TestFunc() {
    startedAt := time.Now()
    defer fmt.Println(time.Since(startedAt))

    time.Sleep(time.Second)
}

func main() {
    TestFunc()
}

This example calculates the execution time of a function, sleep 1s as a simulation, and then the execution result is: 199ns, which is obviously not the expected result. The expected execution time should be greater than 1s. So what is the problem? Because the call to defer pre-calculates the value of the function argument, i.e. the value of the argument time.Since(startedAt), rather than the value calculated after sleep has finished executing. This could be changed to match expectations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "time"
)

func TestFunc() {
    startedAt := time.Now()
    defer func() {
        fmt.Println(time.Since(startedAt))
    }()

    time.Sleep(time.Second)
}

func main() {
    TestFunc()
}

The defer followed by the anonymous function is sufficient and meets our expectations. The result of the execution is: 1.003861399s.

defer and panic

There are three times when the execution of a defer is triggered.

  • The function wrapping the defer encounters return
  • The function wrapping the defer is executed to the end
  • when a panic occurs

Let’s look at what happens when a defer meets a panic.

No exceptions caught in defer when panic occurs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func TestFunc() {
    defer func() {
        fmt.Println("A")
    }()

    defer func() {
        fmt.Println("B")
    }()

    panic("panic occurred")
}

func main() {
    TestFunc()
}

Running results.

1
2
3
4
B
A
panic: panic occurred
... // panic 堆栈信息

Catch exception in defer when panic occurs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func TestFunc() {
    defer func() {
        fmt.Println("A")
    }()

    defer func() {
        fmt.Println("B")
        if err := recover(); err != nil {
            fmt.Println("catch panic")
        }
    }()

    panic("panic occurred")
}

func main() {
    TestFunc()
}

Running results.

1
2
3
B
catch panic
A

There’s not much to say about the result of this example, a panic occurs and can be caught and handled in the defer function.

panic occurs in defer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func TestFunc() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Printf("catch %s panic\n", err)
        }
    }()

    defer func() {
        panic("inner")
    }()

    panic("outer")
}

func main() {
    TestFunc()
}

The result is: catch inner panic. i.e. panic(“outer”) occurs in the outer layer, triggers the execution of the defer, panic(“inner”) occurs in the execution of the defer, and panic(“inner”) occurs in the last The panic is caught in the defer, and the last panic is caught.

Also, try not to use panic, and only use it if a non-recoverable error occurs or if something unexpected happens when the program starts.