Confused about defer

218 views
Skip to first unread message

dc0d

unread,
Aug 25, 2016, 6:19:17 PM8/25/16
to golang-nuts
Assuming we have this test:

func TestRecover(t *testing.T) {
 f
:= func() (interface{}, error) {
 panic
(`TEST`)
 
}
 r
, e := Recover(f)
 t
.Log(r, e)
}

And two versions of Recover function.

Version 1:
func Recover(f func() (interface{}, error)) (interface{}, error) {
 
var result interface{}
 
var err error

 defer func
() {
 
if e := recover(); e != nil {
 err
= errors.Error(fmt.Sprintf("%v", e))
 
}
 
}()

 result
, err = f()

 
return result, err
}


Version 2:
func Recover(f func() (interface{}, error)) (res interface{}, err error) {
 defer func
() {
 
if e := recover(); e != nil {
 err
= errors.Error(fmt.Sprintf("%v", e))
 
}
 
}()

 res
, err = f()

 
return res, err
}


Question: Why the output of test for Version 1 is <nil> <nil> (not expected/wrong) but for Version 2 is <nil> TEST (as expected/correct)?

- Go 1.7, Ubuntu 14.04 x64

Vasko Zdravevski

unread,
Aug 25, 2016, 6:55:11 PM8/25/16
to golang-nuts
I put your code snippet in the playground for easier sharing: https://play.golang.org/p/ZvuNwjS7ZF

I think the spec has the answer you're looking for regarding how named result parameters are handled during a panic.
https://golang.org/ref/spec#Defer_statements

Specifically the sentence:
If the deferred function has any return values, they are discarded when the function completes. (See also the section on handling panics.)

Also,

If the function's signature declares result parameters, the function body's statement list must end in a terminating statement.

https://golang.org/ref/spec#Function_declarations

And the panic is the 'terminating statement'

Hope this helps,
Vasko.

dc0d

unread,
Aug 26, 2016, 3:11:19 AM8/26/16
to golang-nuts
There deferred function here has not any return value - to get discarded. It rather assigns a value to the closure err variable.

Since a defer statement "invokes a function whose execution is deferred to the moment the surrounding function returns", so I expected the err variable should have a value just before the return statement executes.

The err variable is just a closure. Still I'm confused why the first version does not behave as expected - because the actual function is not invoked yet at the position of defer statement but just it's parameters. "Instead, deferred functions are invoked immediately before the surrounding function returns".

Why this is not behaving as expected?

Axel Wagner

unread,
Aug 26, 2016, 3:41:03 AM8/26/16
to dc0d, golang-nuts
I'd think, the spec is reasonably unambiguous, if not very explicit. The second version works, because of

For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned.

and

The recover function allows a program to manage behavior of a panicking goroutine. Suppose a function G defers a function D that calls recover and a panic occurs in a function on the same goroutine in which G is executing. When the running of deferred functions reaches D, the return value of D's call to recover will be the value passed to the call of panic. If D returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between G and the call to panic is discarded, and normal execution resumes. Any functions deferred by G before D are then run and G's execution terminates by returning to its caller.

i.e. after recover(), the execution continues as normally and the normal behavior is, that you can modify named return parameters and the modified version will then be returned.

The first version also works as expected: recover() resumes execution as normal and you can modify result and error as wished, but they are just variables, not return values. Thus the function returns with the return parameters unset (as you panic'ed before setting them with return).

If it helps, you can imagine the return values as variables, initialized to their zero values, which can be set in two ways: Either by having a return or by naming them, which gives you direct access. A panic won't modify them, but a defer'ed function might, if they are named. In your first version, you never set them; return isn't called and they are not named. In the second version you do set them; return isn't called, but you name them and set them from the defer'ed function.

Maybe the confusion is about what "after the surrounding function returns" means? Maybe you interpret that as basically a "goto the line of the return statement and execute it", whereas the spec means it as "executing the function epilogue and return control to the caller"? The goto-like interpretation doesn't make sense, in any case; there could be multiple, pairwise contradictory return statements and it wouldn't be clear which is executed.

FWIW I do believe the spec could be clearer here. The behavior seems logical and expected to me, but I do have trouble justifying this intuition with the spec.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

pierre...@gmail.com

unread,
Aug 26, 2016, 3:52:25 AM8/26/16
to golang-nuts
To me this works as expected.

In both your versions, the return statement in Recover() is not even reached since the call to f panics. You recover from the panic in your defer statement and assign the err variable your error value.

Since in your first version, that variable is not returned, you do not see it outside of your Recover() function.
In your second version, the err variable is declared in your function signature, so it does get returned, hence why you see it.

dc0d

unread,
Aug 26, 2016, 4:28:20 AM8/26/16
to golang-nuts, kaveh.sh...@gmail.com
Yes; it seemed to me that the return statement would get evaluated even after a panic. I thought of it as a special case of a deferred context - confused.

Thank you very much;
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages