A Go Puzzle: Returning without return
Here is a very simple Go program:
package main
import "fmt"
func f() int {
return 42
}
func main() {
fmt.Println(f())
}
As expected, it simply prints “42” on the standard output.
The challenge
Now, I have a challenge for you: rewrite the function f
so that:
- it is still a function
- when called, it still returns the
int
42 - but it does not contain the keyword
return
at all
In other words, you must not touch the main
function.
The program’s behavior is unchanged.
It still compiles correctly, runs and promptly prints out “42”.
There just isn’t any return
in f
(or anywhere else!) anymore.
Give it a Go!
Maybe you know about named results and thought “easy, I juste have to write something like that:”
func f() (res int) {
res = 42
}
Alas, no. Compiler doesn’t agree: “missing return”.
You would still need at least a bare return:
func f() (res int) {
res = 42
return
}
But we don’t want no stinky return
!
I’ll write a couple hints below and the answer at the end. But you should try solving the puzzle first! Click here to open the Go playground and give it a go!
BTW, there aren’t any tricks in the wording of the problem.
You’re truly looking for a way to write a function that returns a result without any return
. That’s it.
It doesn’t depend on a compiler bug or a particular implementation or having to use CGO or manipulate the stack or whatever dirty tricks.
It really is 100% pure, proper Go, that should work with any Go version and environment.
(Hints below…)
.
.
.
.
.
.
.
.
.
Hints
Use the spec, Luke!
As said above, this is proper Go. So it’s allowed by the language. So it’s in the spec!
Take a look at these two very similar functions. One actually compiles, and one doesn’t:
func compilesOK() {
// no body, no return, no nothing, still compiles!
}
func doesntCompile() int {
// compiler says: missing return
}
Can you find the reason why in the spec?
.
.
.
.
.
.
.
.
.
Terminating statements
The answer to the question above is in the Function declarations section:
If the function’s signature declares result parameters, the function body’s statement list must end in a terminating statement.
doesntCompile
declares an int
result parameter, so its body must end in a terminating statement.
There is no such requirement for compilesOK
, which explains the difference.
Terminating statements are listed in the aptly-named Terminating statements section.
You can see that there is the obvious return
.
But also others!
A surprising one is an infinite for
loop.
So this function compiles!
func compilesOK() int {
for {}
}
… but of course, an infinite loop never returns!
Can you find a terminating statement that makes the function actually terminate?
Other than return
of course!
.
.
.
.
.
.
.
.
.
Panic
panic
is a built-in function that terminates the execution of a function.
It is also a terminating statement!
So this function compiles!
func compilesOK() int {
panic(42)
}
But it doesn’t return 42, it makes the function panic.
If you called that function in the main
’s fmt.Println()
, it would just make the whole program terminate with a panic, not print “42” as we want.
Can you find a way to stop the function from actually panicking?
(this was the last hint! Solution below)
.
.
.
.
.
.
.
.
.
The solution
By combining the powers of panic
, recover
and named results, we can write this function that returns without a return
:
func f() (res int) {
defer func() { recover() }()
res = 42
panic("whatever")
}
Proof here :)