On Thu, Jan 11, 2018 at 8:10 AM, 'Eric Johnson' via golang-nuts <
golang-nuts@googlegroups.com> wrote:

> I was looking at fixing an issue flagged by gometalinter, and realized I
> didn't understand the actual behavior of defer statements. Specifically, I
> was not handling the error return of a close operation.
>
> I wrote a test program so I could understand the behavior, which led to
> these two function
>
> package main
>
> import "fmt"
>
> func checkDefer(newVal int, val *int) {
> fmt.Printf("current value is %v\n", *val)
> *val = newVal
> }
>
> func main() {
> fmt.Printf("variableOnUnnamedReturn value is %v, expected 30\n",
> variableOnUnnamedReturn(20, 25, 30))
> fmt.Printf("variableOnNamedReturn value is %v, expected 45\n",
> variableOnNamedReturn(35, 40, 45))
> }
>
> func variableOnUnnamedReturn(start, test, def int) int {
> retval := start
> defer checkDefer(def, &retval)
> retval = test
> return retval
> }
>
> func variableOnNamedReturn(start, test, def int) (retval int) {
> retval = start
> defer checkDefer(def, &retval)
> retval = test
> return retval
> }
>
>
> The body of the two functions differs only by a single ":", and yet, the
> output of the Printf calls differs. This appears to be due to very subtle
> differences between how named and unnamed returns are handled in the
> context of defer statements.
>

There are basically two reasons
<https://github.com/golang/go/wiki/CodeReviewComments#named-result-parameters>
to use named return values: 1) Documentation, 2) Do what you are doing here.
It is interesting, though, that you find it confusing. To me, it always
seemed kind of natural, that a defered function executes *after* the
return. If you accept that part, the behavior should be very clear, IMO.

I looked through the language spec, and did not see this clearly spelled
> out.
>

It's spelled out here <https://golang.org/ref/spec#Defer_statements>, that
defered functions can modify named returns. Though you are right that it's
not explicitly spelled out whether they execute before, after or concurrent
with the return statement.


> It is not obvious to me, a relatively experience Go developer, that the 
> "variableOnUnnamedReturn"
> function should return a 25 in the above use, but the "variableOnNamedReturn"
> function returns a 45 in the above use.
>

> As near as I can tell, operationally, the "unnamed" return could be
> re-written in something like the following fashion (where addDeferredOps is
> a collector for deferred operations.):
>
>         retval := start
>         dos := addDeferredOps(func() { checkDefer(def, &retval)} )
>         retval = test
>         _retval1_ = retval
>         dos.performDeferredOps()
>         return _retval1_
>
> Which makes it obvious that the call to checkDefer() won't change the
> return of the function. Whereas, the "named return" version effectively
> amounts to this:
>
>         retval := start
>         dos := addDeferredOps(func() { checkDefer(def, &retval)} )
>         retval = test
>         dos.performDeferredOps()
>         return retval
>
> I read the language spec, and simply didn't see the description of the
> difference between these two behaviors. Seems mostly like a language bug,
> to me. Feels like the named and unnamed return cases should behave the same.
>

I don't agree. Currently, the sequence of a return is

1. The expressions in the return statement are evaluated and stored as
return values (named or unnamed)
2. All defered functions are executed (potentially modifying the named
returns)
3. Control is returned

What you'd want amounts to

1. All defered functions are executed (potentially modifying the named
returns)
2. All expressions in the return statement are evaluated and stored as
return values
3. Control is returned

Not only do I not see how this would be simpler to explain - it also opens
up some other problems.
For example, when I use named returns and modify them, I usually do it in this
pattern <https://play.golang.org/p/iERVXiy5Yq7>. In the changed sequence of
events, this would return "closing failed", instead of "stuff failed". And
how would this <https://play.golang.org/p/VDt3A0lnFN7> work? recover can
only work, after the returned expressions where evaluated; but the defer
executes before that.

The alternative would be, to somehow make return not actually return
values, but to have something like
1. The expressions in the return statement are evaluated as references
2. All defered functions are executed
3. The references from the return statement are dereferenced and the values
stored as return values
4. Control is returned
But for that to work, you'd need to only allow addressable expressions in
return (which would be bad) or you would need to somehow introduce a new,
weird way to describe the sequence of events. Either way, this doesn't seem
simpler to explain, yet again.


> I can see why they don't (expressions on the return line, instead of
> simple uses of the variables), but that doesn't make me happy about having
> to explain the difference to others on my team.
>

I agree that there is *some* subtlety here, but I also believe it's easier
to explain than the alternatives:
1) Return values are stored somewhere that isn't a local variable. You can
name that storage, or don't, but it always exists
2) with := you are declaring a new, local variable.
3) return evaluates its expressions and puts them into the return storage
4) defered functions are executed after return, but before control is
returned/panic'ing continues.

Seems doable.


> Eric.
>
>
>
> --
> 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+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to