Is it safer or more idiomatic to issue multiple defer
statements which are dependent on the order, or to defer an anonymous function which packages the logic?
Examples:
defer os.Remove(tempFile.Name()) defer tempFile.Close()
In the case above the syntax is minimal, yet the order of defers is reverse to the logic to be executed.
In the case below there are more lines, more "syntax", but the logic is in a more natural order:
defer func() { tempFile.Close() os.Remove(tempFile.Name()) }()
Which one to use?
In Go language, defer statements delay the execution of the function or method or an anonymous method until the nearby functions returns. In other words, defer function or method call arguments evaluate instantly, but they don't execute until the nearby functions returns.
Order of execution The deferred call's arguments are evaluated immediately, even though the function call is not executed until the surrounding function returns. If there are several deferred function calls, they are executed in last-in-first-out order.
defer statement is a convenient way to execute a piece of code before a function returns, as explained in Golang specification: Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.
A defer statement defers the execution of a function until the surrounding function returns. The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.
In this example, the anonymous function is easier to read, especially once you add in error handling.
f, err := ioutil.TempFile("", "prefix") if err != nil { log.Println("creating temp file:", err) return } defer func() { err := f.Close() if err != nil { log.Println("close:", err) } err = os.Remove(f.Name()) if err != nil { log.Println("remove:", err) } }()
If you have multiple resources, then multiple defer
s is generally appropriate.
As Ross Light answer states:
If you have multiple resources, then multiple defers is generally appropriate.
April 2019: But in that case, consider Go 1.13 (Q4 2019), as it does integrate a fix for go issue 14939: "runtime: defer is slow" and go issue 6980: "cmd/compile: allocate some defers in stack frames"
See Go CL 171758: "cmd/compile,runtime: allocate defer records on the stack"
When a defer is executed at most once in a function body, we can allocate the defer record for it on the stack instead of on the heap.
This should make defers like this (which are very common) faster.
This optimization applies to 363 out of the 370 static defer sites in the cmd/go binary.
name old time/op new time/op delta Defer-4 52.2ns ± 5% 36.2ns ± 3% -30.70% (p=0.000 n=10+10)
Oct. 2019 (Go 1.13 is released a few weeks ago)
This is confirmed (Brad Fitzpatrick) with CL 190098:
Cost of defer statement [
go test -run NONE -bench BenchmarkDefer$ runtime
]
With normal (stack-allocated) defers only: 35.4 ns/op With open-coded defers: 5.6 ns/op Cost of function call alone (remove defer keyword): 4.4 ns/op
But Damien Grisky adds:
Defer gets cheaper, but panic/recover is more expensive.
Cost of defer: 34ns -> 6ns. Cost of panic/recover: 62ns -> 255ns
That is not a bad trade-off.
In other words, while using multiple defer can be idiomatic, that practice was held back by performance costs which are no longer a concern with Go 1.13+.
(as illustrated by Paschalis's blog post "What is a defer? And how many can you run?")
That makes practical use if defer (in places where a function call should be executed irrespective of the code flow) possible.
John Refior notes, however, that defer
is synchronous:
Actually defer is executed immediately before the function exits.
And it occurs synchronously, so the caller waits for defer to complete.
So even if you can now have multiple defer, make sure they are fast, or, as John notes:
Fortunately it’s easy to wrap a goroutine in a
defer
, giving us the flow control and timing we want, without delaying the caller:
func Handler(w http.ResponseWriter, r *http.Request) { log.Println("Entered Handler") defer func() { go func() { time.Sleep(5 * time.Second) log.Println("Exiting goroutine") }() log.Println("Exiting defer") }() }
Often defers are used for locking a mutex, or closing a connection or file descriptor, and the work they do is fast, or we want it to complete before the caller moves on.
But when you’re doing slow work that the client shouldn’t need to wait for at the end of an HTTP handler, making the call asynchronous can substantially improve user experience.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With