Skip to content

Instantly share code, notes, and snippets.

@qbig
Last active August 17, 2018 10:18
Show Gist options
  • Select an option

  • Save qbig/94f36c69ca4ad1385202186d23852717 to your computer and use it in GitHub Desktop.

Select an option

Save qbig/94f36c69ca4ad1385202186d23852717 to your computer and use it in GitHub Desktop.
Golang Defer is Weird

Normal case: Defer make sure something important will be called always

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()  // might not be called
    src.Close() // might not be called
    return
}

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close() // better

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close() // better

    return io.Copy(dst, src)
}

Other uses of defer (beyond the file.Close example given earlier) include releasing a mutex:

mu.Lock()
defer mu.Unlock()

printing a footer:

printHeader()
defer printFooter()

Weird 1. A deferred function's arguments are evaluated when the defer statement is evaluated.

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}
// print 0

Weird 2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}
// prints "3210":

Weird 3. Deferred functions may read and assign to the returning function's named return values.

func c() (i int) {
    defer func() { i++ }()
    return 1
}
// return 2

Panic and Recover

Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

print

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment