Why does go panic recover to return value with local variable not work? - go

This panic recover code works with named return values.
func main() {
result, err := foo()
fmt.Println("result:", result)
if err != nil {
fmt.Println("err:", err)
}
}
func foo() (result int, err error) {
defer func() {
if e := recover(); e != nil {
result = -1
err = errors.New(e.(string))
}
}()
bar()
result = 100
err = nil
return
}
func bar() {
panic("panic happened")
}
Output
result: -1
err: panic happened
But why this code with local variables does not work?
func main() {
result, err := foo()
fmt.Println("result:", result)
if err != nil {
fmt.Println("err:", err)
}
}
func foo() (int, error) {
var result int
var err error
defer func() {
if e := recover(); e != nil {
result = -1
err = errors.New(e.(string))
}
}()
bar()
result = 100
err = nil
return result, err
}
func bar() {
panic("panic happened")
}
Output
result: 0
Any explanation to help me understanding the reason / basic concept of it? In the go tour basics the explanation is as followed.
Named return values
Go's return values may be named. If so, they are treated as variables defined at the top of the function.
So it should be the same, right?

Note that this has nothing to do with panic/recover, it is a feature of the defer statement.
... 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. If the deferred function has
any return values, they are discarded when the function completes.

Spec: Return statements details this:
There are three ways to return values from a function with a result type:
The return value or values may be explicitly listed in the "return" statement. Each expression must be single-valued and assignable to the corresponding element of the function's result type.
The expression list in the "return" statement may be a single call to a multi-valued function. The effect is as if each value returned from that function were assigned to a temporary variable with the type of the respective value, followed by a "return" statement listing these variables, at which point the rules of the previous case apply.
The expression list may be empty if the function's result type specifies names for its result parameters. The result parameters act as ordinary local variables and the function may assign values to them as necessary. The "return" statement returns the values of these variables.
So basically if you use a return statement that explicitly lists the return values, those will be used, regardless if the result parameters are named or not.
If the result parameters are named, they act as ordinary local variables: you can read and write them. If the result parameters are named, you may use a "naked" return statement, without listing the values to return. If you do so, then the actual return values will be the values of the (named) result parameters. The same thing applies if your function does not reach a return statement due to panicing and recovering: once the deferred functions run, the actual return values will be the values of the named result parameters (which the deferred functions can change and "have a say" in what to return).
If you don't use named result parameters but you declare local variables, they are not special in this way: when the function returns, those are not used "automatically" as the result values (like they would be if they would be named result parameters and not local variables). So if you change them in a deferred function, that will not have any effect on the actual values returned. In fact, if you don't use named result parameters and your function panics and recovers, you can't specify the return values, they will be the zero values of the result types. That's why you see result: 0 (0 is the zero value for int) and no error (because error is an interface type and zero value for interface types is nil and you don't print the error if it's nil).
See related: How to return a value in a Go function that panics?

Might be a brief summary for #icza's anwser:
Named return variables use their final values for returning when the function teminate with no panic(return normally or recover from panic), so you can change them in defer recover func(), and the final values changed, so be the return values.
If use local variables, compiler can not know these local variables will be used as return variables until a normal return. Local variables might be changed in panic recover, but
the return statement has not been executed yet because the panic, so the local variables you defined was not treated as return variables, the return values will be the zero values of the return types.

Related

How to handle errors when they are NOT the only value returned by a function?

I have created a function to handle errors that looks like this:
func handleErr(e error) {
if e != nil {
log.Fatal("Error:", e)
}
}
For functions that only return an error, I can do the following:
handleErr(os.Chdir("Documents"))
However, for functions that also return other values and not only an error, I have to spread my code over two lines, like this:
s, e := os.Getwd()
handleErr(e)
Is there a way to condense the above two lines into one?
Please note that it's not possible to uniformly handle the error and return a dynamically typed value at the same time. handleErr() could only return a value of type interface{}, further type assertion would be needed to extract a concrete type from it. As noted by mkopriva's comment, generics is needed to make the return type "dynamic".
The below answer only handles the error, but discards the other return value(s).
If you can modify handleErr()
Add another parameter to handlerErr() of interface{} type. Any value is assignable to interface{}:
func handleErr(i interface{}, e error) {
if e != nil {
log.Fatal("Error:", e)
}
}
Lets use this example function:
func foo(fail bool) (int, error) {
if fail {
return 0, errors.New("fail")
}
return 0, nil
}
Testing it:
handleErr(foo(false))
fmt.Println("First is OK")
handleErr(foo(true))
fmt.Println("Second is OK")
Output (try it on the Go Playground):
First is OK
2009/11/10 23:00:00 Error:fail
handleErr(foo(false)) is possible and is valid, because Spec: Calls:
As a special case, if the return values of a function or method g are equal in number and individually assignable to the parameters of another function or method f, then the call f(g(parameters_of_g)) will invoke f after binding the return values of g to the parameters of f in order. The call of f must contain no parameters other than the call of g, and g must have at least one return value. If f has a final ... parameter, it is assigned the return values of g that remain after assignment of regular parameters.
If you can't modify handleErr()
You can write a helper function which reduces 2 return values to one:
func reduceTwoParams(i interface{}, err error) error {
return err
}
You may use this with any functions that return 2 values where second is error, because all values are assignable to interface{}.
Testing it:
handleErr(reduceTwoParams(foo(false)))
fmt.Println("First is OK")
handleErr(reduceTwoParams(foo(true)))
fmt.Println("Second is OK")
Output (try it on the Go Playground):
First is OK
2009/11/10 23:00:00 Error:fail
If you want to handle functions with 3 return values, you have to write another helper:
func reduceThreeParams(i, j interface{}, err error) error {
return err
}

Why can a normal return hide a panic that a named return correctly provides to the caller? [duplicate]

This question already has answers here:
How to return a value in a Go function that panics?
(3 answers)
Closed 3 years ago.
package main
import (
"fmt"
"log"
)
func catch(err *error) {
if r := recover(); r != nil {
*err = fmt.Errorf("recovered panic: %v", r)
}
}
func panicIf42(n int) {
if n == 42 {
panic("42!")
}
}
func NormalReturns(n int) error {
var err error
defer catch(&err)
panicIf42(n)
return err
}
func NamedReturns(n int) (err error) {
defer catch(&err)
panicIf42(n)
return
}
func main() {
err := NamedReturns(42)
log.Printf("NamedReturns error: %v", err)
err = NormalReturns(42)
log.Printf("NormalReturns error: %v", err)
}
output:
2009/11/10 23:00:00 NamedReturns error: recovered panic: 42!
2009/11/10 23:00:00 NormalReturns error: <nil>
Playground link
NormalReturns returns a nil error, but I would expect both NamedReturns and NormalReturns to return a non-nil error.
I thought named returns was just a code readability feature that declares and initializes returns for you, but it seems there's more to it. What am I missing?
I thought named returns was just a code readability feature that declares and initializes returns for you, but it seems there's more to it. What am I missing?
If you name the result parameters, their actual value at the time of returning to the caller will determine the returned values. Meaning you can change their values like other local variables, and if the expression list of the return statement is empty, their last assigned values will be used. Also if there are deferred functions, they can modify the values of the named result parameters after the return statement and before the function returns to its caller, and those modifications will be preserved. It also allows to modify return values in case of a panic, see How to return a value in a Go function that panics?
Spec: Return statements:
Regardless of how they [the return values] are declared, all the result values are initialized to the zero values for their type upon entry to the function. A "return" statement that specifies results sets the result parameters before any deferred functions are executed.
And Spec: Defer statements:
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.
In NormalReturns(): The return value is initialized to its zero value (which is nil for all interface types, including the builtin error type), and since the return statement is not reached (due to a panic in panicIf42()), it will stay nil. It doesn't matter if the local variable err is changed, that is not the result variable. It's just an ordinary variable. It will have no effect on the value returned
In general, if a function does not have named result variables, and if this function does not reach a return statement (e.g. due to a panic), it cannot have return values other than (meaning different from) the zero values of the result types.
In NamedReturns() the deferred catch() will modify the named result variable err. The changes are "preserved": whatever the named result variables hold will be returned when the function ends (which happens after calling deferred functions, if there are any). So even though the return statement is not reached here either, the catch() function changes the err result variable, and whatever is assigned to it will be used as the value returned.
More on the topic:
Go blog: Defer, Panic and Recover:
Deferred functions may read and assign to the returning function's named return values.
And also in Effective Go: Recover:
If doParse panics, the recovery block will set the return value to nil—deferred functions can modify named return values.

Why I get 0 and 1 in the following golang code example with defer

Call to defer produces different results for variables declared in two different ways
package main
import (
"fmt"
)
func c(i int) int {
defer func() { i++ }()
return i
}
func c1() (i int) {
defer func() { i++ }()
return i
}
func c2() (i int) {
defer func() { i++ }()
return 2
}
func main() {
fmt.Println(c(0)) // Prints 0
fmt.Println(c1()) // Prints 1
fmt.Println(c2()) // Prints 3 Thank you icza
}
https://play.golang.org/p/gfnnCZ--DkH
In the first example i is an (incoming) parameter. At the return statement the return value is evaluated, and the deferred function runs after this, and incrementing i in that has no effect on the return value.
In the second example i is the name of the result parameter. At the return statement you explicitly return the value i, which is then assigned to the return value i (this is a no-op). But deferred functions are allowed to modify the values of the return "variables", and if they do so, that will have an effect on the actual returned values.
This becomes clearer if we add another example:
func c2() (i int) {
defer func() { i++ }()
return 2
}
This function will return 3, because the return 2 statement will assign 2 to i, then the deferred function will increment this, and so the return value will be 3. Try this one on the Go Playground. Relevant part from the Spec: Return statements:
A "return" statement that specifies results sets the result parameters before any deferred functions are executed.
In general, if a function (or method) has named result parameters, the return values will always be the values of those variables, but must not forget that a return statement may assign new values to these result paramteters, and they may be modified by deferred functions after a return statement.
This is mentioned in the Spec: Defer statements:
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.
It is also mentioned in the blog post Defer, Panic and Recover:
Deferred functions may read and assign to the returning function's named return values.
And also in Effective Go: Recover:
If doParse panics, the recovery block will set the return value to nil—deferred functions can modify named return values.
See related question: How to return a value in a Go function that panics?

What is the difference between named return value and normal return value?

My question is about the different named return value vs normal return value.
my code
package main
import "fmt"
func main() {
f := fmt.Println
f(a())
f(b())
}
func a() int {
i := 0
defer func() {
i += 1
fmt.Println("a defer : ", i)
}()
return i
}
func b() (i int) {
i = 0
defer func() {
i += 1
fmt.Println("b defer : ", i)
}()
return i
}
the result:
the a function return 0
the b function reutrn 1
Why?
The named return value also allocates a variable for the scope of your function.
func a() int: While you already return the value of i = 0, but since no named values was defined the static value got returned. So even though you're increasing i in the deferred function it doesn't affect the returned value.
func b() (i int): The variable i is allocated (and already initialized to 0). Even though the deferred function runs after the i = 0 was returned the scope is still accessible and thus still can be changed.
Another point of view: you can still change named return values in deferred functions, but cannot change regular return values.
This especially holds true in the following example:
func c() (i int) {
defer func() {
i = 1
fmt.Println("c defer : ", i)
}()
return 0
}
defer runs a function after the return statement but before the function is auctually returned, thus enabling modify returned results. However, only named return results can be accessed normally, i.e. by the variable name.
The return statement, when not naked (another thing about named return, but irrelevant here), the expression got evaluated. And if the return is named, the named variable is assigned with the evaluated value.
In your code, in func a() int the return is typed but not named. So when return i is execuated, it sets the return value, a variable not available to the code, as the value of i. You can consider it as RETVAL := i. And later, your deferred function modified i but the return value (RETVAL) remains unchanged.
But in func b() (i int), i is a named return. Thus, when return i execuate, it literally translate to i = i. And later, your deffered function modified i, a return value, so the returned value change.
More on return: https://golang.org/ref/spec#Return_statements
With the named return value you directly modify what gets returned, with the "normal" return value you just modify local variable in the scope of your function, which never gets returned.
More on this:
Deferred function can access named return values but it has no return value itself - so this is actually the only way to modify main function results from there. Very useful thing.
Imagine you want to fix code which panics - you want it to return the error instead. You can solve it by using recover in a deferred function and then assigning recovered error to named return value.
Example, somewhat abstract but hopefully useful:
func noMorePanics() (err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
potentiallyPanickingFunction()
}

Empty return in func with return value in golang [duplicate]

This question already has answers here:
How does defer and named return value work?
(3 answers)
Closed 5 years ago.
I was reading some code written in Golang on Github and found a very interesting piece of code. I simplified it to be clear.
func Insert(docs ...interface{}) (err error) {
for i := 0; i < 3; i++ {
err = fmt.Errorf("")
if err.Error()!="EOF" {
return
}
}
return
}
I'm very confused about empty return here... How it works? Does he return nil as error or breaks for loop? I understand that this question looks dummy, but I cannot find any info on this in go docs... Also, I don't understand how we can return err, which is, as I understood, declared somehow in return. Does (err error) means that we already have an error variable available in our func which is used as default return value if none specified? Why then we implicitly make return err at the end of func?
I'll be very gratefull for explanation.
The function uses a "named" return value.
From the spec on return statements:
The expression list may be empty if the function's result type
specifies names for its result parameters. The result parameters act
as ordinary local variables and the function may assign values to them
as necessary. The "return" statement returns the values of these
variables.
Regardless of how they are declared, all the result values are
initialized to the zero values for their type upon entry to the
function. A "return" statement that specifies results sets the result
parameters before any deferred functions are executed.
Using named returns allows you to save some code on manually allocating local variables, and can sometimes clean up messy if/else statements or long lists of return values.
func a()(x []string, err error){
return
}
is really just shorthand for
func a() ([]string,error){
var x []string
var err error
return x,err
}
Its a bit shorter, and I agree that it may be less obvious.
Named returns are sometimes needed, as it allows things like accessing them inside a deferred function, but the naked return is just syntactic sugar as far as I can tell, and is never strictly required.
One place I see it commonly is in error return cases in functions that have many return values.
if(err != nil){
return
}
return a,b,c,nil
is easier than
if(err != nil){
return nil,nil,nil,err
}
return a,b,c,nil
when you have to write it a bunch of times. And you don't have to modify those returns if you change the signature to have additional "real" return values.
Most places I am using them in the codebase I just searched, they definitely seem to be hiding other smells, like overly complex multi-purpose functions, too deep if/else nesting and stuff like that.
Go's return values may be named. If so, they are treated as variables defined at the top of the function.
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
https://tour.golang.org/basics/7
When you have a named return value (err here):
func Insert(docs ...interface{}) (err error) {
This creates a function-local variable by that name, and if you just call return with no parameters, it returns the local variable. So in this function,
return
Is the same as, and implies,
return err
This is detailed in the tour and in the spec.

Resources