0%

Go_Fundamental_Learning_Defer Function Call

Definition

defer statement is the keyword of go language. defer statement would delay the execution of function, method and anonymous method until the innermost call function return. Attention please, the arguments of defer function evaluate instantly while the execution is delay. Example is shown in the below part.

Basic Usage

Using the keyword defer before a function call, to imply a delayed ‘function’. The ‘function’ means 1. Function call, 2. Method call , 3. Anonymous method call.

Usage Format

1
defer funcName(arguments)

Usage With Different Object

1
2
3
4
5
6
7
8
9
10
11
12
13
// Function
defer func func_name(parameter_list Type)return_type{
// Code
}

// Method
defer func (receiver Type) method_name(parameter_list){
// Code
}

defer func (parameter_list)(return_type){
// code
}()

Here is a example to show the basic usage of defer:

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

import "fmt"

// Functions
func mul(a1, a2 int) int {

res := a1 * a2
fmt.Println("Result: ", res)
return 0
}
func show() {
fmt.Println("Hello, Defer!")
}

func main() {
mul(1, 9)

defer mul(9, 9)

show()
}

// Result: 9
// Hello, Defer!
// Result: 81

Usage Scenario

Clean up Resource

In programming, remember clean up all the resources(close file , disconnect tcp etc) is not a easy thing. With defer keyword the clean up part will always be executed. Here is a close file example, which compare defer difference with different code style:

  • without defer keyword, close file clause is duplicate:

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

    import (
    "io"
    "log"
    "os"
    )

    func main() {
    if err := write("readme.txt", "This is a readme file"); err != nil {
    log.Fatal("failed to write file:", err)
    }
    }

    func write(fileName string, text string) error {
    file, err := os.Create(fileName)
    if err != nil {
    return err
    }
    _, err = io.WriteString(file, text)
    if err != nil {
    file.Close()
    return err
    }
    file.Close()
    return nil
    }

  • with defer keyword, the complier always remember close file;

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

    import (
    "io"
    "log"
    "os"
    )

    func main() {
    if err := write("readme.txt", "This is a readme file"); err != nil {
    log.Fatal("failed to write file:", err)
    }
    }

    func write(fileName string, text string) error {
    file, err := os.Create(fileName)
    if err != nil {
    return err
    }
    _, err = io.WriteString(file, text)
    defer file.Close() // defer keyword
    if err != nil {
    return err
    }
    return nil
    }

    Deal With recover function

The example above is a complicated and non-idiomatic way to handle runtime problems that would have been better dealt with by just passing error values. I understand that there are definitely edge-cases where use of panic() and recover() might make sense. That said, I’ve been writing Go professionally for about 5 years now and I’ve never felt a sufficient need, especially in application code. Do your best to refactor your project so you can just return errors like the good designers intended.

_Lane Wagner

Clean up operation is not necessary with defer keyword, for a well programmed code could achieve the same effect. While defer is the necessary part of build-in function recover .

In go language, the panic and recover mechanism is used to stop panic spread. If a panicking goroutine exit without being recovered, it will cause the whole program crash. In other language, try/catch mechanism has the similar effect. Thanks to the recover mechanism, the below goroutine did not collapse.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
)

func recoverWithMessage() {
if r := recover(); r != nil {
fmt.Println("recovered from", r)
}
}

func fullName(firstName *string, lastName *string) string {
defer recoverWithMessage()
if firstName == nil {
panic("first name cannot be nil")
}
if lastName == nil {
panic("last name cannot be nil")
}
return fmt.Sprintf("%s %s\n", *firstName, *lastName)
}

func main() {
firstName := "Sun"
lastName := "Qingzhi"
fmt.Println(fullName(&firstName, &lastName))
fmt.Println(fullName(&firstName, nil))
fmt.Println(fullName(nil, nil))
}
// Sun Qingzhi

// recovered from last name cannot be nil

// recovered from first name cannot be nil

Feature

  • Call Sequence of Defer Function

    The Sequence of Defer Function is FILO.

    1
    2
    3
    Actually the goroutine hold two function call stack;
    - For a deferred function call, the invocation moment is the moment when it is pushed into the defer-call stack of its caller goroutine.
    - For a goroutine function call, the invocation moment is the moment when the corresponding goroutine is created.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package main

    import "fmt"

    // Functions
    func mul(a1, a2 int) int {

    res := a1 * a2
    fmt.Println("Result: ", res)
    return 0
    }

    func main() {
    mul(1, 9)
    defer mul(6, 9)
    defer mul(5, 9)
    defer mul(4, 9)
    defer mul(3, 9)
    defer mul(2, 9)
    }
    // Result: 9
    // Result: 18
    // Result: 27
    // Result: 36
    // Result: 45
    // Result: 54
  • Could Modify Named Return Result of Nesting Functions.

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

    import "fmt"

    func Triple(n int) (r int) {
    defer func() {
    r += n // modify the return value
    }()

    return n + n // <=> r = n + n; return
    }

    func main() {
    fmt.Println(Triple(5)) // 15
    }

    Evaluation Moment

defer function call would evaluate the arguments instantly and delayed the execution of function body.

1
2
Unlike other higher-order functions in Go, when you “pass” a function to the defer keyword, you pass an entire function call, not just the name of the function. This allows the function’s arguments to be evaluated immediately. The defer keyword just ensures that the body of the function won’t run until the parent function returns.
_Lane Wagner
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"

// Functions
func mul(a1, a2 int) int {

res := a1 * a2
fmt.Println("Result: ", res)
return 0
}

func main() {
a := 9
defer mul(a, 9)
a = 1
fmt.Printf("The argument evaluatment is instantly,\nNow a is %v, \n", a)
}
// The argument evaluatment is instantly,
// Now a is 1,
// Result: 81