go

Why is Go's "defer" scoped to function, not lexical enclosure?


I was surprised to find that these two programs produce the same output:

Program A

package main
import "fmt"

func main() {  
    defer fmt.Println(1)
    defer fmt.Println(2)
}  //prints 2 1

Program B

package main
import "fmt"

func main() {  
    {
        defer fmt.Println(1)
    }
    defer fmt.Println(2)
}  //prints 2 1

In other words, the "defer" statement appears to disregard lexical closures [edit: Thanks to @twotwotwo for correcting my terminology, I meant to say "block" not "lexical closure"] and is strictly scoped to the function. I wondered:

  1. is my understanding correct?
  2. is there a way to scope it to the block so that it triggers upon exiting the closure, not the function?

I can imagine doing several units of work in sequence, each requiring its own resource to be closed before proceeding... would be nice not to have to break them into separate functions solely for that purpose.


Solution

    1. Is my understanding correct?

    Yes.

    1. Is there a way to scope it to the block [...]?

    There is no way to change how defer works. Depending on the problem you are trying to solve, splitting the function (first example below) or defining anonymous functions (second example) would make it behave like you wanted. The latter is maybe a bit difficult to read. Using named function vars inline (third example) is perhaps more readable.

    More on defer at Go Spec.

    Split

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        fmt.Println("main")
        defer fmt.Println("defer from main")
    
        f()
    }
    
    func f() {
        fmt.Println("f")
        defer fmt.Println("defer from f")
    }
    
    main
    f
    defer from f
    defer from main
    

    → playground

    Anonymous functions

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        fmt.Println("outer func")
        defer fmt.Println("defer from outer func")
    
        func() {
            fmt.Println("first inner func")
            defer fmt.Println("defer from first inner func")
        }()
    
        func() {
            fmt.Println("second inner func")
            defer fmt.Println("defer from second inner func")
        }()
    }
    
    outer func
    first inner func
    defer from first inner func
    second inner func
    defer from second inner func
    defer from outer func
    

    → playground

    Function variables

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        fmt.Println("outer func")
        defer fmt.Println("defer from outer func")
    
        first := func() {
            fmt.Println("first inner func")
            defer fmt.Println("defer from first inner func")
        }
    
        second := func() {
            fmt.Println("second inner func")
            defer fmt.Println("defer from second inner func")
        }
    
        first()
        second()
    }
    
    outer func
    first inner func
    defer from first inner func
    second inner func
    defer from second inner func
    defer from outer func
    

    → playground