Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple defers vs deferred anonymous function

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?

like image 894
Vlad Didenko Avatar asked Sep 12 '15 17:09

Vlad Didenko


People also ask

What is the point of Defer in go?

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.

Is defer executed before return?

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.

Can we return in defer Golang?

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.

What is defer statement?

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.


2 Answers

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 defers is generally appropriate.

like image 191
Ross Light Avatar answered Sep 28 '22 10:09

Ross Light


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.

like image 22
VonC Avatar answered Sep 28 '22 10:09

VonC