Proposal for an exception-like mechanism

3266 views
Skip to first unread message

Rob 'Commander' Pike

unread,
Mar 25, 2010, 2:36:52 AM3/25/10
to golang-nuts Nuts
This proposal differs from the usual model of exceptions as a control structure, but that is a deliberate decision. We don't want to encourage the conflation of errors and exceptions that occur in languages such as Java.

Instead, this proposal uses a slight change to the definition of defer and a couple of runtime functions to provide a clean mechanism for handling truly exceptional conditions.

Another proposal may happen regarding handling/monitoring exceptions in goroutine A from the safety of goroutine B, but that is not part of this proposal and may not happen at all, although we believe it may be achievable entirely by library code given this proposal.

Proposal
-----

First, we tweak the definition of defer. At the moment, it's defined to run deferred functions after the result values are copied back to the caller's stack. We change the definition slightly to say they run after the return values are evaluated but before they are copied to the caller's stack. This makes the output variables available to deferred closures. Thus a deferred closure can modify the return values of the caller of the deferring function.

The function

func numDefer() (n int) {
for i := 0; i < 100; i++ {
defer func() {
n++
}()
}
return
}

will return 100. With the existing spec, it would return 0, if it weren't also illegal (the closure was not allowed to refer to n at all due to the prohibition on taking the address of return values; that has been lifted too).

[This much is now checked in to the spec.]

Next we define a pair of predefined functions:

func panic(v interface{})
func recover() (v interface{})

[This is a redefinition of panic, and we delete panicln altogether.]

The function panic begins a process called panicking. Panicking unwinds the deferred calls, executing each in turn. In other words, each function on the stack, including the one that calls panic, is terminated, in reverse order, with its deferred calls executing as they terminate.

During panicking, if a deferred function invocation calls recover, recover returns the value passed to panic and stops the panicking. At any other time, or inside functions called by the deferred call, recover returns nil. After stopping the panicking, a deferred call can panic with a new argument, or the same one, to continue panicking. Alternately, the deferred call might edit the return values for its outer function, perhaps returning an error.

Here is a simple example.

func f(dopanic bool) {
defer func() {
if x := recover(); x != nil {
fmt.Printf("panicking with value %v\n", v)
panic(x) // go back to panicking
}
fmt.Printf("function returns normally")
}()
fmt.Printf("before")
p(dopanic)
fmt.Printf("after")
}

func p(dopanic bool) {
if dopanic {
panic(3)
}
}

If the function f is called with dopanic=true, it will print

before
panicking with value 3

and generate a stack dump showing that f called function p which called panic. Otherwise, it will print

before
after
function returns normally

and return without crashing.

If we remove the call to panic in f, and call it with dopanic=true, it will print

before
panicking with value 3
function returns normally

and not generate a stack trace.

Since a deferred function may modify named return parameters, a function may recover from a panic and signal that it has done so through its return value. For instance, given

func f(dopanic bool) (err os.Error) {
defer func() {
if x := recover(); x != nil {
err = x.(os.Error)
}
}()
q(dopanic)
return
}

func q(dopanic bool) {
if dopanic {
panic(os.ErrorString("bad karma"))
}

}

If dopanic is true, f will return the error "bad karma"; otherwise it will return nil.

As mentioned above, the recover function can return non-nil only when it is executed directly by a deferred function while panicking. This allows panics to stack and avoids a problem in cases such as the one illustrated here:

func foo() {
defer func() {
if bar() != nil {
os.Exit(0)
}
}()
panic(3)
}

func bar() (err os.Error) {
defer func() {
if x := recover(); x != nil {
fmt.Printf("bar is panicking")
err = x.(os.Error)
}
}()
if horribleNewProblemHappens() {
panic("disaster")
}
return nil
}

Here we see foo call panic, which then calls bar during the deferred function. We do not want bar to act as if it has triggered a panic itself; it should only return an error if there has been a horrible new problem. This is the reason that recover can return non-nil only when executed directly in a deferred closure while the stack is unwinding and not while functions are called in defer blocks.

Implementation of defer, panic, and recover in gc
-----

Defer
-----

This is gc's implementation of the original defer (before any of the recent spec changes):

Each goroutine has a deferred call frame stack on which deferred calls are pushed.
Each entry records:

SP at time of defer, aka SP of func that did the defer
PC of function to call
raw frame holding function arguments

A function that contains a defer statement ends with a special epilogue. Instead of
the normal

ADDQ $framesize, SP
RET

it ends with

CALL rundefer(SB)
ADDQ $framesize, SP
RET

Rundefer processes each entry on the top of the deferred call frame stack
that matches the current SP, writing each to the runtime stack and then
running that function.

If a deferred call inspects the call stack, it looks like it was called directly from f.

Allowing function return values to be addressed or stored in closures
means copying them to the heap on function entry (just like addressed
parameters) and then copying them back to the caller before the RET.
The copy back to the caller must happen after the call to rundefer.
That is the only change to support the editing of return values.


Panic and recover
-----

Each goroutine has a panic stack on which values passed to panic are pushed.
Each entry records:

SP at time of panic
value passed to panic

To panic, a goroutine starts processing entries on the defer stack, one at a time,
just as rundefer would. Recover consults the panic stack: if the caller's SP matches
the SP of the entry atop the stack, recover pops the entry off the stack and returns
the value from that entry. Otherwise recover returns nil.

The processing of a particular panic stops when a deferred call pops the
corresponding entry off the panic stack and does not replace it (by invoking panic).
When that happens, the original call stack is unwound to the SP associated with
the deferred call that stopped the panic, making it appear that the function that
executed the corresponding defer statement is returning. (This might in turn
cause the execution of other deferred code, in a non-panicking context.)

The processing of a panic also stops when the deferred call stack has emptied.
If the latter happens, the program crashes, printing the value passed to panic
and then the full stack trace at the time the panic started.


Recursive panics
-----

If a child of a deferred call panics, or if a deferred call panics without first calling
recover, a new panic begins that applies to the current call stack, including the
deferred call that was handling the first panic. Thus:

func f() {
defer func() {
panic("second")
print("not reached")
}
panic("first")
}

panics twice: the "first" panic runs the deferred func, which causes the "second"
panic. The second panic aborts execution of the code in which it appears,
like panic always does, so the print is not reached. In this program, the second
panic will empty the deferred call stack, causing the program to crash.
When it does, it prints the stacked panic values, oldest first, and then the stack trace.


Stack traces
-----

runtime.Caller works inside deferred code just as it does inside normal code.
During normal execution, the caller of deferred code is the function that
executed the defer statement. During a panic, the caller of deferred code
is panic itself. During a recursive panic, earlier panics are visible on the
call stack. For example, this program:

package main

import (
"fmt"
"runtime"
"strings"
)

// Print names of functions on call stack.
func stack(msg string) {
fmt.Print(msg, ":")
var stk [10]uintptr
n := runtime.Callers(2, stk[0:])
for _, pc := range stk[0:n] {
s := runtime.FuncForPC(pc).Name()
if i := strings.Index(s, "."); i >= 0 { // cut package name
s = s[i+1:]
}
fmt.Print(" ", s)
if s == "main" {
break
}
}
fmt.Println()
}

func main() {
f()
}

func f() {
stack("in f")
defer g()
panic("first")
}

func g() {
stack("in g")
defer h()
panic("second")
}

func h() {
stack("in h")
panic("third")
}

prints

in f: f main
in g: g panic f main
in h: h panic g panic f main

and then the program crashes, printing

panic: first
panic: second
panic: third

main.h+0x3e /home/rsc/x.go:35
main.h()
runtime.panic+0x45 /home/rsc/g/go/src/pkg/runtime/runtime.c:25
runtime.panic()
main.g+0x36 /home/rsc/x.go:29
main.g()
runtime.panic+0x45 /home/rsc/g/go/src/pkg/runtime/runtime.c:25
runtime.panic()
main.f+0x36 /home/rsc/x.go:23
main.f()
main.main+0x1d /home/rsc/x.go:18
main.main()
mainstart+0xf /home/rsc/g/go/src/pkg/runtime/386/asm.s:83
mainstart()

Note that the panics are printed oldest first, but the stack trace is, as always,
newest frame first.

Being able to print the stack trace after running the deferred calls does not
require expensive operations like copying the stack when a panic happens:
all the deferred calls run like ordinary function calls, leaving the original
state at the time of panic intact. The unwinding of caller frames only happens
when a deferred call stops the panic, if at all.


roger peppe

unread,
Mar 25, 2010, 4:06:50 AM3/25/10
to Rob 'Commander' Pike, golang-nuts Nuts
nice.

will this mechanism enable handling of runtime.Goexit?

Kevin Ballard

unread,
Mar 25, 2010, 5:06:24 AM3/25/10
to roger peppe, Rob 'Commander' Pike, golang-nuts Nuts
I like it.

Do defer statements work inside of anonymous functions? In other
words, can we simulate a try/catch block by doing something like

func foo() {
doSomethingHere()
func() {


defer func() {
if x := recover(); x != nil {

fmt.Printf("panic caught with value %v\n", x)
}
}()
doSomethingThatCouldPanic()
}()
fmt.Println("finished foo")
}

If so, we could even write a function to do the catching for us, as in

func catch(f func()) (v interface{}) {


defer func() {
if x := recover(); x != nil {

v = x
}
}()
f()
}

This would then be used to rewrite foo like

func foo() {
doSomethingHere()
x := catch(func() {
doSomethingThatCouldPanic()
})
if x != nil {
fmt.Printf("panic caught with value %v\n", x)
}
fmt.Println("finished foo")
}

--
Kevin Ballard
http://kevin.sb.org
kbal...@gmail.com

Giles Lean

unread,
Mar 25, 2010, 7:48:24 AM3/25/10
to golang-nuts Nuts

Rob 'Commander' Pike <r...@google.com> wrote:

> This proposal differs from the usual model of exceptions as
> a control structure, but that is a deliberate decision. We
> don't want to encourage the conflation of errors and
> exceptions that occur in languages such as Java.

Ab initio disclaimer: I confess I am not a fan of exceptions.
Personally I'd be just as happy if exceptions are not added to
Go.

I _especially_ don't want exceptions to become an oft-used
alternative to multiple levels of error return, as at that
point they deteriorate to action at a distance and make
understanding large code bases much harder. Been there, done
that, got the scars to prove it. (Mostly from C++ and not
from Java, but I've seen enough Java to have a healthy fear of
runtime exceptions.)

Rob -- I've only read your proposal twice so far, and will
read it again and think about it some more after some sleep,
but I have two questions already after that long winded
introduction, based on the immediately practical question:
"What would (or should) change in my existing code if this
proposal was implemented?"

Herewith:

a) Will there be a way other than relying on package
documentation or reviewing a package's source code to tell
whether a package may call panic(), and in particular which
function(s) in the package might do so?

b) Will there be any indication (e.g. a compiler warning) when
I call a function which may panic without setting up an
appropriate defer() beforehand?

Specific to the details of the proposal, the recovery
mechanism looks sane, and the whole proposal looks clear and
is admirably short for what is usually a language feature with
tricky semantics and chapters worth of description. I salute
you.

I look forward to reviewing the proposal again and following
the mailing list discussion, and may have more comments later.

In the meantime, if anyone can either point me to answers to
my questions in the proposal (in which case I'll say "Oops,
sorry" very nicely) or if Rob (and/or the Google Go team?)
would consider the questions, I'll be appreciative.

I am extremely impressed with Go so far: I've written a few
thousand lines (reimplementing some small applications,
essentially as a learning exercise) and with a few exceptions
(sorry, pun unintended) to do with signal handling and pushing
past cgo's limits almost each time I touch that tool, I have
found Go to be an easy and productive language to write in.

(I have some ideas about the signal package which I'll write
up once I'm finished thinking them through. I'm not so sure
about cgo; my ideas there still need some more time to
mature.)

Best regards,

Giles
--

Ian Lance Taylor

unread,
Mar 25, 2010, 9:57:23 AM3/25/10
to Kevin Ballard, roger peppe, Rob 'Commander' Pike, golang-nuts Nuts
Kevin Ballard <kbal...@gmail.com> writes:

> Do defer statements work inside of anonymous functions?

Yes.

> If so, we could even write a function to do the catching for us, as in

Yes.

You could also write a similar wrapper around the go statement,
sending the panic value back on a channel.

Ian

Ian Lance Taylor

unread,
Mar 25, 2010, 10:26:45 AM3/25/10
to Giles Lean, golang-nuts Nuts
Giles Lean <giles...@pobox.com> writes:

> I _especially_ don't want exceptions to become an oft-used
> alternative to multiple levels of error return, as at that
> point they deteriorate to action at a distance and make
> understanding large code bases much harder.

I agree.


> a) Will there be a way other than relying on package
> documentation or reviewing a package's source code to tell
> whether a package may call panic(), and in particular which
> function(s) in the package might do so?

That is a fair question, and the answer is no. In particular,
although it is not part of this proposal, I think that if this
proposal is adopted it is fairly likely that a runtime error like an
out of bounds access to a slice or an erroneous type assertion would
become a panic. (Perhaps division by zero would also become a panic
if that can be done efficiently.)


> b) Will there be any indication (e.g. a compiler warning) when
> I call a function which may panic without setting up an
> appropriate defer() beforehand?

Probably not. After all, there is nothing wrong with letting the
panic propagate upward in the call stack, nor even with letting it
crash your program if that is a reasonable course of action.


The name panic was chosen in part because it connotes a truly unusual
condition. As you say, wide use of an exception mechanism makes a
program harder to understand. At the same time, particularly in the
parallel server model which is easy to write in Go, there is clearly
an advantage to giving a program a way to let a goroutine fail without
causing the entire program to crash. The panic function also provides
a way to return an error up several levels in a call stack without
testing the error at each step, but the expectation there is certainly
that some closely related function would recover and return a more
reasonable error indication.

I personally think that a library function which intentionally panics
and forces the caller to handle the panic is poorly designed. Of
course nothing in this proposal prevents somebody from writing such a
function. It's reasonable to ask whether the language can provide
some mechanism to prevent it. This is just a proposal at this point,
and if anybody has a clever suggestion here we would certainly
consider it.

That said, still speaking purely personally, I don't think exception
specifications as in C++ or Java are appropriate for Go. I don't
think they have been succesful in those languages, and indeed they are
being considered for deprecated in the new C++0x standard. Exceptions
specifications can work well in theory; in practice, however, their
main effect seems to be to force people to write extra code to evade
the specification requirements. There is no reason to import that
approach into a new language.

(The C++0x committee is considering adding a noexcept keyword which
may be applied to a function. I think that what it means in effect is
that the function is wrapped in a catch handler which crashes the
program. We could such a feature to Go, but I'm not sure it really
carries its weight.)

Ian

Giles Lean

unread,
Mar 25, 2010, 11:48:07 AM3/25/10
to Ian Lance Taylor, golang-nuts Nuts

Hi Ian,

Thanks for your answers.

Ian Lance Taylor <ia...@google.com> wrote:

> Giles Lean <giles...@pobox.com> writes:
>
> > a) Will there be a way other than relying on package
> > documentation or reviewing a package's source code to tell
> > whether a package may call panic(), and in particular which
> > function(s) in the package might do so?
>
> That is a fair question, and the answer is no.

Thanks, question answered, and it's probably the appropriate
choice.

> In particular, although it is not part of this proposal, I think that
> if this proposal is adopted it is fairly likely that a runtime error
> like an out of bounds access to a slice or an erroneous type assertion
> would become a panic. (Perhaps division by zero would also become a
> panic if that can be done efficiently.)

Now, those could be useful: I can imagine a defer() function picking one
of those up and giving useful (and likely different) data to both the
user of the application (who probably can't fix it) and the programmer
(who usually wants to).

Users find stack traces very unappealing, however useful they are
to programmers!

(Ian -- are you trying to sell me on exceptions being useful after
all? Hmm ....)

> > b) Will there be any indication (e.g. a compiler warning) when
> > I call a function which may panic without setting up an
> > appropriate defer() beforehand?
>
> Probably not. After all, there is nothing wrong with letting the
> panic propagate upward in the call stack, nor even with letting it
> crash your program if that is a reasonable course of action.

Pretty much what I expected. Indeed, I suspect if this proposal is
adopted it'll be common to declare a suitable defer() function early in
a goroutine's existence, just to take over control of the error
reporting so a compiler warning probably wouldn't help much: if there's
no appropriate defer(), there probably wasn't meant to be one.

> The name panic was chosen in part because it connotes a truly unusual
> condition.

I'm familiar -- one might say overly familiar -- with the term from 8+
years of kernel support for a Unix vendor. If used in similar
circumstances and as little as possible, I don't really object. If
you've painted yourself into a corner and can't levitate, you're out of
options, after all.

I would like to think there are fewer cases for libraries to panic than
for kernels to panic, of course, but good library design is non-trivial.

(And certainly during development panic wouldn't hurt in a language
without assertions; making it blatantly obvious to developers and Q&A
departments that "yes, we know this doesn't work yet" isn't nutty when
they hit something that's not implemented yet. Woe to the developer and
reviewers who let one of those out into a product though!)

> I personally think that a library function which intentionally panics
> and forces the caller to handle the panic is poorly designed.

I concur. (I've fought similar arguments about library functions that
spat error messages out to what they *thought* was standard error.
Library functions shouldn't do that, either.)

> Of course nothing in this proposal prevents somebody from writing such
> a function. It's reasonable to ask whether the language can provide
> some mechanism to prevent it. This is just a proposal at this point,
> and if anybody has a clever suggestion here we would certainly
> consider it.

I suspect nobody will come up with a clever enough suggestion, because
sometimes it may be the library that has something panic-worthy happens.
One example would be a "can't happen" case in a library triggered by
hardware data corruption: a panic would sometimes not be out of order
there, any more than it would be within a kernel.

(A "can't happen" based on corrupt input would be another matter:
that would just be an error, IMHO. Programs should expect and be
prepared for corrupt input.)

> That said, still speaking purely personally, I don't think exception
> specifications as in C++ or Java are appropriate for Go.

+1

In both languages I've found them a PITA, as I've had to code all this
extra "stuff" without much knowledge of what any of the individual
exception types was going to mean if I got one, at least until I buit up
an understanding of the underlying code. (Yeah, I have worked on too
many projects that have had totally inadequate documentation.)

> I don't think they have been succesful in those languages, ...

+1 also, but your knowledge of C++ and Java is well ahead of mine.

Thanks for the response. It answered of both my questions, and provided
more background to Rob's proposal (which I now suspect is not just Rob's
proposal :-).

Cheers,

Giles

meswaknets like

unread,
Mar 25, 2010, 11:54:34 AM3/25/10
to Giles Lean, Ian Lance Taylor, golang-nuts Nuts
SEND MORE

2010/3/25 Giles Lean <giles...@pobox.com>:

> To unsubscribe from this group, send email to golang-nuts+unsubscribegooglegroups.com or reply to this email with the words "REMOVE ME" as the subject.
>

Rob 'Commander' Pike

unread,
Mar 25, 2010, 12:21:13 PM3/25/10
to roger peppe, golang-nuts Nuts

On Mar 25, 2010, at 1:06 AM, roger peppe wrote:

> nice.
>
> will this mechanism enable handling of runtime.Goexit?

Hadn't thought of it but it shouldn't be hard to do. Goexit is like
panic without failure.

-rob

roger peppe

unread,
Mar 25, 2010, 12:32:24 PM3/25/10
to Rob 'Commander' Pike, golang-nuts Nuts
yeah, i'd imagined that there might be a special type or
value (or both) (e.g. runtime.Exiting) which means "don't really panic"
when the stack is unwound completely.

Goexit would just be:

func Goexit() {
panic(runtime.Exiting)
}

if you have this then no-one can evade a defer
without taking down the whole program.

Daniel Smith

unread,
Mar 25, 2010, 12:33:36 PM3/25/10
to Rob 'Commander' Pike, roger peppe, golang-nuts Nuts
Will there still be a way to obtain the stack trace that it currently prints out? (Especially important if the panic source is a divide by zero or other runtime failure condition)

To unsubscribe from this group, send email to golang-nuts+unsubscribegooglegroups.com or reply to this email with the words "REMOVE ME" as the subject.



--
Daniel Smith
http://www.schaumburggoclub.org/

Michael Hoisie

unread,
Mar 25, 2010, 12:34:57 PM3/25/10
to golang-nuts
I think this is a solid plan. In my experience, the two types of
crashes I see most often are invalid map access, and null pointer
exceptions. Would these be recoverable? What kind of data would they
carry?

- Mike

On Mar 25, 7:26 am, Ian Lance Taylor <i...@google.com> wrote:

Rob 'Commander' Pike

unread,
Mar 25, 2010, 12:36:35 PM3/25/10
to Daniel Smith, roger peppe, golang-nuts Nuts

On Mar 25, 2010, at 9:33 AM, Daniel Smith wrote:

> Will there still be a way to obtain the stack trace that it
> currently prints out? (Especially important if the panic source is a
> divide by zero or other runtime failure condition)

Absolutely. Part of the design (see the implementation discussion) is
to guarantee you don't lose stack trace information.

-rob


Daniel Smith

unread,
Mar 25, 2010, 12:45:03 PM3/25/10
to Rob 'Commander' Pike, roger peppe, golang-nuts Nuts
Ah, sorry, I somehow missed that section. This will be a very important feature for anyone who doesn't want their webserver to go down...

Russ Cox

unread,
Mar 25, 2010, 12:54:17 PM3/25/10
to Michael Hoisie, golang-nuts
On Thu, Mar 25, 2010 at 09:34, Michael Hoisie <hoi...@gmail.com> wrote:
> I think this is a solid plan. In my experience, the two types of
> crashes I see most often are invalid map access, and null pointer
> exceptions. Would these be recoverable? What kind of data would they
> carry?

recoverable in the sense that you could abort some
number of function call frames and move on with
the rest of your program.

not recoverable in the sense that you could do something
to let the goroutine continue at the point of fault.

you shouldn't see many more invalid map accesses,
by the way. the next release will change the behavior
of m["no-such-key"] to return a zero value instead of
crash/panic. that change is already in tip.

russ

Esko Luontola

unread,
Mar 25, 2010, 3:50:54 PM3/25/10
to golang-nuts

Will it be possible to get the stack trace programmatically? My use
case is a testing framework, where the code under test throws an
exception, and the testing framework must collect the stack trace into
a string, which will then some time later be shown in the test results
(the stack trace can't be printed to stdout immediately because of the
way that the framework works).

Can I use runtime.Callers() and runtime.FuncForPC() to generate
exactly the same stack trace as would happen when the program
terminates? Where is the code that Go is currently using for
formatting the stack trace? I would like my testing framework to print
stack traces using the same format, because it's familiar to the
developers.

David Roundy

unread,
Mar 25, 2010, 3:51:38 PM3/25/10
to Rob 'Commander' Pike, golang-nuts Nuts
Looks great to me, too! I especially like how it reuses the existing
concept of the defer function, making it considerably more powerful,
in the process. And the Goexit approach sounds great, so that when
there is important cleanup to be done, one can pretty well guarantee
it'll get done. e.g. setting the terminal mode back to plain old
cooked mode... there's little more annoying than having one's terminal
messed up by a program dying. Well, it's not so annoying if you know
to just type "reset" blind, but for users who don't know that, it's
pretty annoying.

David

Russ Cox

unread,
Mar 25, 2010, 3:57:14 PM3/25/10
to Esko Luontola, golang-nuts
> Can I use runtime.Callers() and runtime.FuncForPC() to generate
> exactly the same stack trace as would happen when the program
> terminates?

Almost. Not quite, because you can't get at the argument values.
But the argument values printed in the panic traces aren't useful
anyway, unless you're comfortable reading the binary representation
of the various data structures. You can certainly make it look
substantially similar. The loss of the argument values is not such
a big deal since they're not really readable as it is.

I think it's safe to say that the interface to the active call stack
is still evolving. It's a little too ad hoc right now.

> Where is the code that Go is currently using for
> formatting the stack trace?

src/pkg/runtime/amd64/traceback.c (even on 386)

Russ

Esko Luontola

unread,
Mar 25, 2010, 4:08:21 PM3/25/10
to golang-nuts

Thanks.

Also the exception proposal looks promising. :)

NoiseEHC

unread,
Mar 25, 2010, 4:56:22 PM3/25/10
to Rob 'Commander' Pike, golang-nuts Nuts
Yeah, this is the lite (and fixed) version of the proposal I just
proposed on 2009.12.09... (panic == exception.Throw and recover ==
exception.GetCurrent + exception.Swallow in my proposal) Your fix is the
modified "defer" semantics, I like it!

However there are some problems with it:

1. There is no default defer mechanism (which would wrap every goroutine
automatically).

2. If a defer is handling a panic and there is a nested panic happens
deep down in the called functions (which can easily happen while logging
the panic), then this nested panic will be re-thrown. So even if some
panic handler defer higher in the call stack could analyze the panic
value and do some error recovery (sometimes it can happen), all it will
recover() the useless nested panic value. This was the reason my
proposal used exception.GetCurrent() and exception.Swallow() to signal
that the exception was handled and kept the nested panic value in a
panic value array and it returned the original one for GetCurrent().
Your proposal swallows the exception at recover() even if it is not yet
handled properly by the defer... While I 100% agree that you should
discourage writing a lot of exception handling code, the result seems a
little bit too much for me:

func f1() {


defer func() {
if x := recover(); x != nil {

defer func() {
// nothing, just catch the panic of logging
}
print("panic happened") // just log, which can panic inside
}
}
panic("error")
}

or this one

func f2() {


defer func() {
if x := recover(); x != nil {

defer func() {
if y := recover(); y != nil {
if x != nil {
panic(x)
}
panic(y)
}
}
print("panic happened") // just log, which can panic inside
x = nil
}
}
panic("error")
}

3. This last one is not a problem with the text of your proposal but
about the ideology of exception handling.
Go gives us sufficient tools to return and check expected errors. Your
proposal is sufficient to return and check for unexpected errors.
Unfortunately it still leaves the definition of expected/unexpected
error to the library which is called. Now there is this little problem
that only the caller code can decide (I mean the developer of the caller
code) what is expected and what is not. Because there is no mechanism in
Go to automatically check errors (this was the longer part of my
proposal), the default code will either just ignore unexpected errors or
check and panic() every failed function call which is ugly... (If you
look at Go code without exceptions, it also just ignores possible error
return values which is a little bit C like.)

Norman Yarvin

unread,
Mar 25, 2010, 5:14:19 PM3/25/10
to Rob 'Commander' Pike, golang-nuts Nuts
On Wed, Mar 24, 2010 at 11:36:52PM -0700, Rob 'Commander' Pike wrote:

>Each goroutine has a panic stack on which values passed to panic are pushed.
>Each entry records:
>
> SP at time of panic
> value passed to panic
>
>To panic, a goroutine starts processing entries on the defer stack, one at a time,
>just as rundefer would. Recover consults the panic stack: if the caller's SP matches
>the SP of the entry atop the stack, recover pops the entry off the stack and returns
>the value from that entry. Otherwise recover returns nil.

Does 'matches' there mean something more than simple equality? From the
rest of the proposal, it seems clear that functions are meant to be able
to catch panics that occur in the functions they call; but in that case
the stack pointers would not be equal. A greater-than comparison could
distinguish between SPs of called functions (from which panics can be
caught) and SPs of callers (from which they're not supposed to be
caught), provided that the stack is a normal stack, with stack frames
ordered by call depth. So maybe that is what is meant by 'matches'?

Russ Cox

unread,
Mar 25, 2010, 5:23:28 PM3/25/10
to Norman Yarvin, Rob 'Commander' Pike, golang-nuts Nuts
> Does 'matches' there mean something more than simple equality?  From the
> rest of the proposal, it seems clear that functions are meant to be able
> to catch panics that occur in the functions they call; but in that case
> the stack pointers would not be equal.

The SP of the function that executes the defer statement is not relevant.
It's the SP of the deferred invocation that must match, because
that's how you can tell it's the top of a deferred invocation as opposed
to a sub-call. It sounds like you understand the intent. The details
may be off slightly - it's a sketch of an implementation that doesn't
yet exist.

Russ

Daniel T

unread,
Mar 25, 2010, 5:30:28 PM3/25/10
to golang-nuts

On Mar 24, 11:36 pm, "Rob 'Commander' Pike" <r...@google.com> wrote:
> Instead, this proposal uses a slight change to the definition of defer and a couple of runtime functions to provide a clean mechanism for handling truly exceptional conditions.

Very elegant.

It looks like testing for panics would incur very little overhead (as
compared to creating a try-catch frame).

Do you have a general idea of what runtime errors could be detected
and what would just crash no matter what? A few were discussed, div
by 0, array index out of bounds...

Regarding:


>Another proposal may happen regarding handling/monitoring exceptions
>in goroutine A from the safety of goroutine B, but that is not part of this
>proposal and may not happen at all, although we believe it may be
>achievable entirely by library code given this proposal.

This would also be very cool. Right now I feel the syntax proposed is
a little bulky, but it is very precise. It seems tempting to creating
a generic "catch" syntax such as:
recover(x) {
printError()
}
rather then defer func() { x :=recover() }
but that may be taken as a C++, Java, C# style error handling. I
guess by writing out exactly what's happening there's much more
control, and these should be few and far between.

If this is able to handle handling and monitoring exceptions in other
goroutines, it sounds great! Otherwise, there may be a syntax that
can work with arbitrary goroutine exceptions... Hmm that thought
needs work.


Norman Yarvin

unread,
Mar 25, 2010, 6:10:20 PM3/25/10
to Giles Lean, Ian Lance Taylor, golang-nuts Nuts
On Fri, Mar 26, 2010 at 02:48:07AM +1100, Giles Lean wrote:

>> The name panic was chosen in part because it connotes a truly unusual
>> condition.
>
>I'm familiar -- one might say overly familiar -- with the term from 8+
>years of kernel support for a Unix vendor.

I don't know... mere naming certainly isn't going to deter me from
misusing the mechanism for ordinary errors. Nor would anything else I
noticed in the proposal. Indeed, with an empty interface as the error
code, I could define my own types for exceptions, and write code that
checked the type returned from recover(), handled only the types of
exceptions that I myself had defined (doing type comparisons via
reflect.TypeOf), and passed anything else on by re-calling panic().
That'd be pretty much guaranteed to not mess with other uses of panic().
It might not even really be worth calling 'misuse'.


Ian Lance Taylor

unread,
Mar 25, 2010, 6:20:06 PM3/25/10
to NoiseEHC, Rob 'Commander' Pike, golang-nuts Nuts
NoiseEHC <Nois...@freemail.hu> writes:

> 1. There is no default defer mechanism (which would wrap every
> goroutine automatically).

This is true but I think it's OK. A simple library function can
implement this for those who need it. Perhaps something like

func Go_and_recover(f func()) chan interface{} {
c := make(chan interface{})
go func() {
defer func() {
c <- recover()
}()
f()
}()
return c
}


> 2. If a defer is handling a panic and there is a nested panic happens
> deep down in the called functions (which can easily happen while
> logging the panic), then this nested panic will be re-thrown. So even
> if some panic handler defer higher in the call stack could analyze the
> panic value and do some error recovery (sometimes it can happen), all
> it will recover() the useless nested panic value. This was the reason
> my proposal used exception.GetCurrent() and exception.Swallow() to
> signal that the exception was handled and kept the nested panic value
> in a panic value array and it returned the original one for
> GetCurrent(). Your proposal swallows the exception at recover() even
> if it is not yet handled properly by the defer... While I 100% agree
> that you should discourage writing a lot of exception handling code,
> the result seems a little bit too much for me:
>
> func f1() {
> defer func() {
> if x := recover(); x != nil {
> defer func() {
> // nothing, just catch the panic of logging
> }
> print("panic happened") // just log, which can panic inside
> }
> }
> panic("error")
> }

This example, and the other one, seems slightly odd to me. If you are
going to log something, then presumably there is no higher level
caller which can handle a panic. So I don't see how the distinction
between GetCurrent and Swallow helps in such a case. Also, you are
assuming that 1) logging something can panic; 2) if it does panic, you
still want to continue program execution. It's not obvious to me that
either assumption is likely to be true. And, as you demonstrate with
your program, if either is in fact true, Go can still handle it,
albeit more verbosely.

I think that one can build GetCurrent and Swallow out of the proposed
panic/recover mechanism, and I think the reverse is true as well.
Since the mechanisms are equally powerful, the question is which is
more likely to be useful in the common case. My initial thought is
that a nested panic is not the common case, so it is not the one to
optimize for. Do you have other examples which argue otherwise?


> 3. This last one is not a problem with the text of your proposal but
> about the ideology of exception handling.
> Go gives us sufficient tools to return and check expected errors. Your
> proposal is sufficient to return and check for unexpected
> errors. Unfortunately it still leaves the definition of
> expected/unexpected error to the library which is called. Now there is
> this little problem that only the caller code can decide (I mean the
> developer of the caller code) what is expected and what is
> not. Because there is no mechanism in Go to automatically check errors
> (this was the longer part of my proposal), the default code will
> either just ignore unexpected errors or check and panic() every failed
> function call which is ugly... (If you look at Go code without
> exceptions, it also just ignores possible error return values which is
> a little bit C like.)

I'm not sure I entirely understand what you are saying. The caller of
a function can always ignore any error that function may be generate.
That does not change with the panic/recover proposal.

Ian

meswaknets like

unread,
Mar 25, 2010, 6:41:33 PM3/25/10
to Ian Lance Taylor, NoiseEHC, Rob 'Commander' Pike, golang-nuts Nuts
SEND MORE

2010/3/26 Ian Lance Taylor <ia...@google.com>:

Giles Lean

unread,
Mar 25, 2010, 7:13:20 PM3/25/10
to golang-nuts Nuts

NoiseEHC <Nois...@freemail.hu> wrote:

> However there are some problems with it:
>

> ...


>
> 2. If a defer is handling a panic and there is a nested
> panic happens deep down in the called functions (which can
> easily happen while logging the panic), then this nested
> panic will be re-thrown.

I was and am uncomfortable about that too. But I'm still
thinking it through; in kernel land nested panics aren't
unusual (and nobody cares: only the first one counts, and
you're not trying to recover). And I don't think I _ever_ saw
more panics than one per CPU core and one extra, and I
wouldn't swear that I ever saw even that many in a single
crash.

On one hand, not allowing nested panics puts us right back
with "Your program exited. Sorry about that. Bye."

On the other hand, I agree the number of times that recovering
from a nested panic will then allow recovery from the initial
panic will be few, but disallowing this case seems harsh.

On the gripping hand, why stop at all? Can we have an
infinite cascade of panics? (Rob -- will we need a hard limit
at which point the runtime does throw up its hands in horror
and just bails out?)

Exceptions are tricky. After the discussion I've seen, I'm
becoming more enthusiastic about the proposal for Go, but
still fear that panic() will be way overused -- after all,
exceptions have been overused and abused in every language I'm
familiar with that has any form of them except perl, and
they're almost under the radar there, and there are plenty of
/other/ unwise things one can do in perl to mess with a
maintainer's sanity.

My take on this is still twofold, presuming the proposal or
a variant goes ahead (which I imagine it will):

a) the design and implementation has to be very, very careful
not to encourage overuse

b) as a community, much as preferred idioms (e.g. don't use an
'else' clause if you've called 'return' in the 'if' clause)
are already developing, we need to recognise and encourage
healthy (and only healthy) use of the new facility whatever
it turns out to be

Giles, possibly being pessimistic. Perhaps eggs sunny side up
will help. I think it's time to go and find out. :-)

Tim Henderson

unread,
Mar 25, 2010, 8:09:32 PM3/25/10
to golang-nuts
I like this proposal. My only concern is a semantic concern where
someone who is reading a function will have a hard time attaching what
the panic handler is attached too. What I mean is writer had an
intention to what the handler belongs too, but it could apply to
anything in the function. If the handler is written correctly this may
not be a problem, however, for the reader it may be difficult to
discern the original intent.

Cheers
Tim

On Mar 25, 7:13 pm, Giles Lean <giles.l...@pobox.com> wrote:

Eleanor McHugh

unread,
Mar 26, 2010, 6:18:21 AM3/26/10
to golang-nuts
Bold generalisation: an exception is nothing more nor less than the statement at one level of scope that the current path of execution has terminated (for good or ill) but that any decision about whether or not to terminate the current thread of execution or the program as a whole should be made at an enclosing level of scope.

I really want the flexibility this understanding allows as it results in cleaner code which is easier to maintain, especially when combined with a helpful syntax. In the Ruby world we have

begin
some_code()
rescue Exception
rescue OtherException
ensure
end

which whilst not perfect does allow different behaviours when different exceptions are thrown (increasingly likely the further the handler is from the scope where the exception is raised) and the ensure clause would be equivalent to the current usage of defer in go but only with regard to the code which might throw the exception.

I'm holding off commenting one way or the other until I've figured out whether or not this pattern can be easily (i.e. maintainably) replicated in go.

However anything which allows me to move execution flow signalling (including errors) out-of-band from function return values would in my opinion be an improvement over the existing approach.

Might I even suggest going one step further and stealing the fail/succeed/cut mechanism from Icon. Go with goal-direction would be a thing of genuine beauty.


Ellie

Eleanor McHugh
Games With Brains
http://feyeleanor.tel
----
raise ArgumentError unless @reality.responds_to? :reason

Mue

unread,
Mar 26, 2010, 8:20:45 AM3/26/10
to golang-nuts
I also know the usage of a surrounding exception handling from Java,
Smalltalk, Erlang, or Python. Funnily even if it is very similar
Erlang uses it by far less than Java. But that's a different topic.

What I like is to place my recovery at the beginning of the context,
without any more surrounding, indentation, and handling later. The
only thing I don't like is the notation. Defer is misused here, the
original idea is ok so. But I would like a statement like recover
taking a func with an empty interface. This func will be called if
there has been a panic().

func ImportantFunc() {
recover func(panic interface{}) { log.Stderrf("Ouch: %v", panic) }

DoSomething()

if IDontKnowWhatToDo() {
panic("I don't know what to do!")
}

DoMoreButOnlyWithNoPanic()
}

I hope I've been able to make my idea clear.

mue

Eero Saynatkari

unread,
Mar 26, 2010, 8:46:25 AM3/26/10
to golan...@googlegroups.com

On 2010-03-26, at 12:18:21, Eleanor McHugh wrote:
> Bold generalisation: an exception is nothing more nor less than the
> statement at one level of scope that the current path of execution
> has terminated (for good or ill) but that any decision about whether
> or not to terminate the current thread of execution or the program
> as a whole should be made at an enclosing level of scope.

I think separating the concepts of flow control and exception or
error handling would be good when considering options for
the system; at the very least so that error handling (the throw
or raise mechanic) is a particular use case of a more general
mechanic rather than the other way around.

I am a big fan of having both available whether separate or
somehow unified, but I am not qualified to comment on which
way, if either, is suitable for go. That said, I think speaking just
of "exception handling" is constructing an artificial box of people's
preconceptions and assumptions that will serve to limit debate.


--
Magic is insufficiently advanced technology.


roger peppe

unread,
Mar 26, 2010, 9:00:11 AM3/26/10
to Mue, golang-nuts
On 26 March 2010 12:20, Mue <m...@tideland.biz> wrote:
> func ImportantFunc() {
>    recover func(panic interface{}) { log.Stderrf("Ouch: %v", panic) }
>
>    DoSomething()
>
>    if IDontKnowWhatToDo() {
>        panic("I don't know what to do!")
>    }
>
>    DoMoreButOnlyWithNoPanic()
> }

the nearest i can get to your one-liner is this:

defer func(){ when(recover(), func(x interface{}){log.Stderrf("Ouch:
%v", x)})}()

which assumes this helper function:

func when(x interface{}, f func(interface{}) {
if x != nil {
f(x)
}
}

but really, recovery blocks should be vastly less common
than panic statements, so surely the following isn't too bad?

defer func() {
if x := recover(); x != nil {

log.Stderrf("Ouch: %v", panic)
}

}()

and if you just want a generic way to catch panics without
doing any special recovery action, then this is quite brief.

defer func(){logPanic(recover())}()

func logPanic(x interface{}) {
if x != nil {
log.Stderrf("Ouch: %v", x)
}
}

Mue

unread,
Mar 26, 2010, 9:17:31 AM3/26/10
to golang-nuts
Surely this isn't too bad. But it's not about a one liner, it's about
maintenance due to a clear purpose int the code It's not the task of a
deferred function to internally test if there probably a panic has
happened during the execution of the outer block. Instead an explicit
recovery function should only be called if it's needed. My proposal
above doesn't need more keywords or controlling structures. But
instead of retrieving and testing for a needed recovery "on the fly" I
would let the runtime handle this.

mue

roger peppe

unread,
Mar 26, 2010, 10:00:18 AM3/26/10
to Mue, golang-nuts
On 26 March 2010 13:17, Mue <m...@tideland.biz> wrote:
> instead of retrieving and testing for a needed recovery "on the fly" I
> would let the runtime handle this.

i partially agree. i think that one flaw in the system as proposed
is, AFAICS, there's a performance penalty imposed in the non-exceptional
case - the code in the defer statement will be run regardless,
because the compiler doesn't distinguish between recovery
blocks and normal defer blocks.

perhaps i'm wrong here. can the compiler be clever enough
to make the following code avoid the closure allocation, function
call etc when b is non-zero?

func div(a, b int) (c int) {
defer func(){
if recover() != nil {
c = 0
}
}()
c = a / b
}

Ryanne Dolan

unread,
Mar 26, 2010, 11:43:43 AM3/26/10
to Rob 'Commander' Pike, golang-nuts Nuts

I don't like how recover() is a call to a closure which somehow magically closes the environment in which it is called. Every other fxn's environment is closed at the time it is defined, not when it is called!

Can we make recover a statement instead?

recover x
if x ...

That way it doesn't look like a fxn call with a magic environment. Instead it is a statement in the deferred fxn's environment.

Ryanne
- from my phone -

On Mar 25, 2010 1:37 AM, "Rob &apos;Commander&apos; Pike" <r...@google.com> wrote:

This proposal differs from the usual model of exceptions as a control structure, but that is a deliberate decision.  We don't want to encourage the conflation of errors and exceptions that occur in languages such as Java.



Instead, this proposal uses a slight change to the definition of defer and a couple of runtime functions to provide a clean mechanism for handling truly exceptional conditions.

Another proposal may happen regarding handling/monitoring exceptions in goroutine A from the safety of goroutine B, but that is not part of this proposal and may not happen at all, although we believe it may be achievable entirely by library code given this proposal.

Proposal
-----

First, we tweak the definition of defer.  At the moment, it's defined to run deferred functions after the result values are copied back to the caller's stack. We change the definition slightly to say they run after the return values are evaluated but before they are copied to the caller's stack.  This makes the output variables available to deferred closures.  Thus a deferred closure can modify the return values of the caller of the deferring function.

The function

       func numDefer() (n int) {
               for i := 0; i < 100; i++ {
                       defer func() {
                               n++
                       }()
               }
               return
       }

will return 100.   With the existing spec, it would return 0, if it weren't also illegal (the closure was not allowed to refer to n at all due to the prohibition on taking the address of return values; that has been lifted too).

[This much is now checked in to the spec.]

Next we define a pair of predefined functions:

       func panic(v interface{})
       func recover() (v interface{})

[This is a redefinition of panic, and we delete panicln altogether.]

The function panic begins a process called panicking.  Panicking unwinds the deferred calls, executing each in turn.  In other words, each function on the stack, including the one that calls panic, is terminated, in reverse order, with its deferred calls executing as they terminate.

During panicking, if a deferred function invocation calls recover, recover returns the value passed to panic and stops the panicking.  At any other time, or inside functions called by the deferred call, recover returns nil.  After stopping the panicking, a deferred call can panic with a new argument, or the same one, to continue panicking.  Alternately, the deferred call might edit the return values for its outer function, perhaps returning an error.

Here is a simple example.

       func f(dopanic bool) {


               defer func() {
                       if x := recover(); x != nil {

                               fmt.Printf("panicking with value %v\n", v)
                               panic(x)  // go back to panicking
                       }
                       fmt.Printf("function returns normally")
               }()
               fmt.Printf("before")
               p(dopanic)
               fmt.Printf("after")
       }

       func p(dopanic bool) {
               if dopanic {
                       panic(3)
               }
       }

If the function f is called with dopanic=true, it will print

       before
       panicking with value 3

and generate a stack dump showing that f called function p which called panic. Otherwise, it will print

       before
       after
       function returns normally

and return without crashing.

If we remove the call to panic in f, and call it with dopanic=true, it will print

       before
       panicking with value 3
       function returns normally

and not generate a stack trace.

Since a deferred function may modify named return parameters, a function may recover from a panic and signal that it has done so through its return value.  For instance, given

       func f(dopanic bool) (err os.Error) {


               defer func() {
                       if x := recover(); x != nil {

                               err = x.(os.Error)
                       }
               }()
               q(dopanic)
               return
       }

       func q(dopanic bool) {
               if dopanic {
                       panic(os.ErrorString("bad karma"))
               }

       }

If dopanic is true, f will return the error "bad karma"; otherwise it will return nil.

As mentioned above, the recover function can return non-nil only when it is executed directly by a deferred function while panicking.  This allows panics to stack and avoids a problem in cases such as the one illustrated here:

       func foo() {
               defer func() {
                       if bar() != nil {
                               os.Exit(0)
                       }
               }()
               panic(3)
       }

       func bar() (err os.Error) {


               defer func() {
                       if x := recover(); x != nil {

                               fmt.Printf("bar is panicking")
                               err = x.(os.Error)
                       }
               }()
               if horribleNewProblemHappens() {
                       panic("disaster")
               }
               return nil
       }

Here we see foo call panic, which then calls bar during the deferred function.  We do not want bar to act as if it has triggered a panic itself; it should only return an error if there has been a horrible new problem.  This is the reason that recover can return non-nil only when executed directly in a deferred closure while the stack is unwinding and not while functions are called in defer blocks.



Implementation of defer, panic, and recover in gc
-----

Defer
-----

This is gc's implementation of the original defer (before any of the recent spec changes):

Each goroutine has a deferred call frame stack on which deferred calls are pushed.
Each entry records:

       SP at time of defer, aka SP of func that did the defer
       PC of function to call
       raw frame holding function arguments

A function that contains a defer statement ends with a special epilogue.  Instead of
the normal

       ADDQ $framesize, SP
       RET

it ends with

       CALL rundefer(SB)
       ADDQ $framesize, SP
       RET

Rundefer processes each entry on the top of the deferred call frame stack
that matches the current SP, writing each to the runtime stack and then
running that function.

If a deferred call inspects the call stack, it looks like it was called directly from f.

Allowing function return values to be addressed or stored in closures
means copying them to the heap on function entry (just like addressed
parameters) and then copying them back to the caller before the RET.
The copy back to the caller must happen after the call to rundefer.
That is the only change to support the editing of return values.


Panic and recover
-----



Each goroutine has a panic stack on which values passed to panic are pushed.
Each entry records:

       SP at time of panic
       value passed to panic

To panic, a goroutine starts processing entries on the defer stack, one at a time,
just as rundefer would.  Recover consults the panic stack: if the caller's SP matches
the SP of the entry atop the stack, recover pops the entry off the stack and returns
the value from that entry.  Otherwise recover returns nil.

The processing of a particular panic stops when a deferred call pops the
corresponding entry off the panic stack and does not replace it (by invoking panic).
When that happens, the original call stack is unwound to the SP associated with
the deferred call that stopped the panic, making it appear that the function that
executed the corresponding defer statement is returning.  (This might in turn
cause the execution of other deferred code, in a non-panicking context.)

The processing of a panic also stops when the deferred call stack has emptied.
If the latter happens, the program crashes, printing the value passed to panic
and then the full stack trace at the time the panic started.


Recursive panics
-----

If a child of a deferred call panics, or if a deferred call panics without first calling
recover, a new panic begins that applies to the current call stack, including the
deferred call that was handling the first panic.  Thus:

       func f() {
               defer func() {
                       panic("second")
                       print("not reached")
               }
               panic("first")
       }

panics twice: the "first" panic runs the deferred func, which causes the "second"
panic.  The second panic aborts execution of the code in which it appears,
like panic always does, so the print is not reached.  In this program, the second
panic will empty the deferred call stack, causing the program to crash.
When it does, it prints the stacked panic values, oldest first, and then the stack trace.


Stack traces
-----

runtime.Caller works inside deferred code just as it does inside normal code.
During normal execution, the caller of deferred code is the function that
executed the defer statement.  During a panic, the caller of deferred code
is panic itself.  During a recursive panic, earlier panics are visible on the
call stack.  For example, this program:

       package main

       import (
               "fmt"
               "runtime"
               "strings"
       )

       // Print names of functions on call stack.
       func stack(msg string) {
               fmt.Print(msg, ":")
               var stk [10]uintptr
               n := runtime.Callers(2, stk[0:])
               for _, pc := range stk[0:n] {
                       s := runtime.FuncForPC(pc).Name()
                       if i := strings.Index(s, "."); i >= 0 { // cut package name
                               s = s[i+1:]
                       }
                       fmt.Print(" ", s)
                       if s == "main" {
                               break
                       }
               }
               fmt.Println()
       }

       func main() {
               f()
       }

       func f() {
               stack("in f")
               defer g()
               panic("first")
       }

       func g() {
               stack("in g")
               defer h()
               panic("second")
       }

       func h() {
               stack("in h")
               panic("third")
       }

prints

       in f: f main
       in g: g panic f main
       in h: h panic g panic f main

and then the program crashes, printing

       panic: first
       panic: second
       panic: third

       main.h+0x3e /home/rsc/x.go:35
               main.h()
       runtime.panic+0x45 /home/rsc/g/go/src/pkg/runtime/runtime.c:25
               runtime.panic()
       main.g+0x36 /home/rsc/x.go:29
               main.g()
       runtime.panic+0x45 /home/rsc/g/go/src/pkg/runtime/runtime.c:25
               runtime.panic()
       main.f+0x36 /home/rsc/x.go:23
               main.f()
       main.main+0x1d /home/rsc/x.go:18
               main.main()
       mainstart+0xf /home/rsc/g/go/src/pkg/runtime/386/asm.s:83
               mainstart()

Note that the panics are printed oldest first, but the stack trace is, as always,
newest frame first.

Being able to print the stack trace after running the deferred calls does not
require expensive operations like copying the stack when a panic happens:
all the deferred calls run like ordinary function calls, leaving the original
state at the time of panic intact.  The unwinding of caller frames only happens
when a deferred call stops the panic, if at all.

Russ Cox

unread,
Mar 26, 2010, 11:45:55 AM3/26/10
to Ryanne Dolan, Rob 'Commander' Pike, golang-nuts Nuts
On Fri, Mar 26, 2010 at 08:43, Ryanne Dolan <ryann...@gmail.com> wrote:
> I don't like how recover() is a call to a closure which somehow magically
> closes the environment in which it is called.

I'm not sure which of the many messages you're replying to,
but in the original proposal, recover is not a call to a magic
closure.

Russ

Ryanne Dolan

unread,
Mar 26, 2010, 12:00:23 PM3/26/10
to r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts

Russ,

In the original proposal, how does recover() return a value which depends on the fxn in which it is called? I realize it isn't actually a closure, but the fxn() syntax makes it look exactly like all other fxn calls. The result of a fxn call only depends on the parameters and its environment, normally. Since there are no parameters, I'm parsing it as a closure with an environment somehow magically linked to the fxn in which it is called.

Does that make sense? Is there a big reason not to make it a statement?

Ryanne

- from my phone -

On Mar 26, 2010 10:45 AM, "Russ Cox" <r...@golang.org> wrote:

On Fri, Mar 26, 2010 at 08:43, Ryanne Dolan <ryann...@gmail.com> wrote:

> I don't like how recove...

roger peppe

unread,
Mar 26, 2010, 12:13:37 PM3/26/10
to Ryanne Dolan, r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts
On 26 March 2010 16:00, Ryanne Dolan <ryann...@gmail.com> wrote:
> The result
> of a fxn call only depends on the parameters and its environment, normally.
> Since there are no parameters, I'm parsing it as a closure with an
> environment somehow magically linked to the fxn in which it is called.

doesn't the current panic state count as part of the environment?

Ryanne Dolan

unread,
Mar 26, 2010, 12:19:23 PM3/26/10
to roger peppe, r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts

Roger,

Exactly, but how does the "recover" fxn access the panic state of the fxn in which it was _called_ ?

Ryanne
- from my phone -

On Mar 26, 2010 11:13 AM, "roger peppe" <rogp...@gmail.com> wrote:

On 26 March 2010 16:00, Ryanne Dolan <ryann...@gmail.com> wrote:
> The result

> of a fxn call onl...

Steven

unread,
Mar 26, 2010, 12:24:18 PM3/26/10
to Ryanne Dolan, r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts
It doesn't depend on the function it is called in, it depends on the
panic state of the thread it is called in.

On Friday, March 26, 2010, Ryanne Dolan <ryann...@gmail.com> wrote:
> Russ,
> In the original proposal, how does recover() return a value which depends on the fxn in which it is called? I realize it isn't actually a closure, but the fxn() syntax makes it look exactly like all other fxn calls. The result of a fxn call only depends on the parameters and its environment, normally. Since there are no parameters, I'm parsing it as a closure with an environment somehow magically linked to the fxn in which it is called.
>
> Does that make sense? Is there a big reason not to make it a statement?
> Ryanne
> - from my phone -
> On Mar 26, 2010 10:45 AM, "Russ Cox" <r...@golang.org> wrote:
>
> On Fri, Mar 26, 2010 at 08:43, Ryanne Dolan <ryann...@gmail.com> wrote:

>> I don't like how recove...I'm not sure which of the many messages you're replying to,

Ryanne Dolan

unread,
Mar 26, 2010, 12:30:36 PM3/26/10
to Steven, r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts

Steven,

Ah that makes sense. I guess I overlooked that panic state is global to a goroutine. Thanks.

Ryanne

- from my phone -

On Mar 26, 2010 11:24 AM, "Steven" <stev...@gmail.com> wrote:

It doesn't depend on the function it is called in, it depends on the
panic state of the thread it is called in.


On Friday, March 26, 2010, Ryanne Dolan <ryann...@gmail.com> wrote:
> Russ,

> In the original pr...

roger peppe

unread,
Mar 26, 2010, 12:39:42 PM3/26/10
to Steven, Ryanne Dolan, r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts
On 26 March 2010 16:24, Steven <stev...@gmail.com> wrote:
> It doesn't depend on the function it is called in, it depends on the
> panic state of the thread it is called in.

actually, it depends on both - i believe recover() must be called
directly within the defer block to catch panics in the code
subsequent to the defer.

but the current function is part of the global environment too,
so i don't see any difficulty there.

Ryanne Dolan

unread,
Mar 26, 2010, 12:47:44 PM3/26/10
to roger peppe, r...@golang.org, Rob 'Commander' Pike, Steven, golang-nuts Nuts

Roger,

The current fxn is not part of the global environment. A fxn cannot tell who called it, and certainly can't access the environment of the caller.
Not in the traditional sense of closures anyway.

Ryanne
- from my phone -

On Mar 26, 2010 11:39 AM, "roger peppe" <rogp...@gmail.com> wrote:

On 26 March 2010 16:24, Steven <stev...@gmail.com> wrote:

> It doesn't depend on the function it i...

roger peppe

unread,
Mar 26, 2010, 12:53:39 PM3/26/10
to Ryanne Dolan, r...@golang.org, Rob 'Commander' Pike, Steven, golang-nuts Nuts
On 26 March 2010 16:47, Ryanne Dolan <ryann...@gmail.com> wrote:
> Roger,
>
> The current fxn is not part of the global environment. A fxn cannot tell who
> called it

erm, runtime.Caller() seems to enable just that.

obviously some of the global environment is hidden by
the runtime - it's still there though, and that's what recover uses.

Ryanne Dolan

unread,
Mar 26, 2010, 1:03:08 PM3/26/10
to roger peppe, r...@golang.org, Steven, Rob 'Commander' Pike, golang-nuts Nuts

Roger,

Right. Runtime.Caller is also a magic function, but at least I expect as much with the runtime package. I'd hate to see magic functions adopted elsewhere.

Ryanne
- from my phone -

On Mar 26, 2010 11:53 AM, "roger peppe" <rogp...@gmail.com> wrote:

On 26 March 2010 16:47, Ryanne Dolan <ryann...@gmail.com> wrote:
> Roger,
>

> The current fxn is ...

Rob 'Commander' Pike

unread,
Mar 26, 2010, 1:08:55 PM3/26/10
to Eleanor McHugh, golang-nuts

On Mar 26, 2010, at 3:18 AM, Eleanor McHugh wrote:

> Bold generalisation: an exception is nothing more nor less than the
> statement at one level of scope that the current path of execution
> has terminated (for good or ill) but that any decision about whether
> or not to terminate the current thread of execution or the program
> as a whole should be made at an enclosing level of scope.
>
> I really want the flexibility this understanding allows as it
> results in cleaner code which is easier to maintain, especially when
> combined with a helpful syntax. In the Ruby world we have
>
> begin
> some_code()
> rescue Exception
> rescue OtherException
> ensure
> end
>
> which whilst not perfect does allow different behaviours when
> different exceptions are thrown (increasingly likely the further the
> handler is from the scope where the exception is raised) and the
> ensure clause would be equivalent to the current usage of defer in
> go but only with regard to the code which might throw the exception.

This is exactly the kind of thing the proposal tries to avoid. Panic
and recover are not an exception mechanism as usually defined because
the usual approach, which ties exceptions to a control structure,
encourages fine-grained exception handling that makes code unreadable
in practice. There really is a difference between an error and what
we call a panic, and we want that difference to matter. Consider
Java, in which opening a file can throw an exception. In my
experience few things are less exceptional than failing to open a
file, and requiring me to write inside-out code to handle such a
quotidian operation feels like a Procrustean imposition.

Our proposal instead ties the handling to a function - a dying
function - and thereby, deliberately, makes it harder to use. We want
you think of panics as, well, panics! They are rare events that very
few functions should ever need to think about. If you want to protect
your code, one or two recover calls should do it for the whole
program. If you're already worrying about discriminating different
kinds of panics, you've lost sight of the ball.

For the peculiarity of problem panic and recover are designed to
address, a little clumsiness feels right.

The subject of this thread is not exceptions, but an exception-like
mechanism. Even that pushes people's thinking too far into the
direction we're trying to steer away from and I regret the error.

panic("these are not exceptions")

-rob

Steven

unread,
Mar 26, 2010, 1:42:14 PM3/26/10
to roger peppe, Ryanne Dolan, r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts

Something related I've been wondering, what about other deferred function calls?

func f1() (success bool) {
defer func() { success = recover() == nil }()
defer f2()
panic("panicking")
}

func f2() {
defer func() { _ = recover() }
}

func f3() { _ = recover() }

Does f1() return true or false? I'd expect false, since it wouldn't
make much sense for a panic to be caught several levels up the call
stack. But then, if you replace f2 with f3, I'd expect true, since f2
is at the same depth as a closure would be, so it it's a matter of
depth wrt the panicker, it should be equivalent. However, if it's
something that gets wrapped by the closure, I'd expect false.

Essentially, how does pancking propogate upward?

Daniel T

unread,
Mar 26, 2010, 3:17:20 PM3/26/10
to golang-nuts
At first I had the same question. But if I read and understood it
correctly, defers caused by normal usage and defers caused by panics
should be able to be intertwined. Consider the following:

defer func() {recover() }()
openFile(..)
defer CloseFile()

var i = 5 / 0 <-- assume runtime throws panic here
---
now defers are still evaluated in a LIFO queue, right?
"defer CloseFile" was last in, thus, first the file gets closed, and
providing nothing there panics,
then recover is called and the function exits.

Thus the defer-recover works at the function level, without hindering
defers written within the function itself.

The only time I would use this is probably when spawning server
responses or clients, or when dynamically loading code (future).
File IO (by itself), IPC, RPC, and any other "unreliable" calls
shouldn't need this syntax, although again, servers would probably
wrap all operations, per transaction, in a defer-recover.

One of the best features of go is the small runtime that tells me when
I'm being stupid (array out of bounds) or when I'm not handling a
known function state.

> Essentially, how does pancking propogate upward?

Rob does show examples of this in his first post. Maybe reread...


On Mar 26, 10:42 am, Steven <steven...@gmail.com> wrote:
> On Friday, March 26, 2010, roger peppe <rogpe...@gmail.com> wrote:

Ian Lance Taylor

unread,
Mar 26, 2010, 4:07:27 PM3/26/10
to Steven, roger peppe, Ryanne Dolan, r...@golang.org, Rob 'Commander' Pike, golang-nuts Nuts
Steven <stev...@gmail.com> writes:

> Something related I've been wondering, what about other deferred function calls?
>
> func f1() (success bool) {
> defer func() { success = recover() == nil }()
> defer f2()
> panic("panicking")
> }
>
> func f2() {
> defer func() { _ = recover() }
> }
>
> func f3() { _ = recover() }
>
> Does f1() return true or false? I'd expect false, since it wouldn't
> make much sense for a panic to be caught several levels up the call
> stack.

It's not an issue of the call stack. The rule is that recover only
returns non-nil if called directly from a deferred function, rather
than from a function called from a deferred function. In this case f2
is a deferred function, but the function that f2 passes to defer is a
function called from a deferred function (though it is of course a
deferred function itself). So in this case the recover called
indirectly from f2 should not see the panic started in f1, so f1
should return false.

> But then, if you replace f2 with f3, I'd expect true, since f2
> is at the same depth as a closure would be, so it it's a matter of
> depth wrt the panicker, it should be equivalent. However, if it's
> something that gets wrapped by the closure, I'd expect false.

Replacing f2 with f3 still causes recover to be called directly from a
deferred function, so here f1 will return true.

Ian

Vish Subramanian

unread,
Mar 26, 2010, 4:25:32 PM3/26/10
to Rob 'Commander' Pike, golang-nuts Nuts
It seems that the following pattern will be common enough that there should be language support for it?

defer func() {
       if x := recover(); x != nil {
          // do recovery
       }
}()

Perhaps a defer_on_panic statement that invokes a function that takes the return of recover() as an argument, and is guaranteed to be invoked only that is non-nil. That is, the statement above could be something like:

defer_on_panic func(x interface{}) {
          // x guaranteed to be non-nil. 
          // do recovery.
}()

Im sure we could write wrappers to achieve the same thing, but  having it built in could help performance since it should be cheaper to invoke the recovery function only on panics, rather than every time.

Vish

Steven

unread,
Mar 26, 2010, 5:58:52 PM3/26/10
to Daniel T, golang-nuts
I see. My phone cut off the bottom of the message, so when I was
rereading it, with that in mind, I didn't recall that part. I'm
finding this paragraph somewhat ambiguous:

"To panic, a goroutine starts processing entries on the defer stack,
one at a time,
just as rundefer would.  Recover consults the panic stack: if the
caller's SP matches
the SP of the entry atop the stack, recover pops the entry off the
stack and returns
the value from that entry.  Otherwise recover returns nil."

What does it mean for the caller's SP to match the SP of the entry
atop the [panic] stack? Does it mean the caller of the closure that
calls recover(), or the direct caller of recover(), and if the latter,
how would that allow for calling recover in a deferred closure (which
is the whole point), and would it allow for calling by a deferred
non-closure function?

Also, how does this matching account for the caller being down the
call stack from the panicker?

Basically, I'm looking for some clarification on where (and how, if at
all possible at this stage) the lines are drawn between panicking and
non-panicking functions in a panic situation. I have general gist of
it, but I'm not entirely clear.

> On Mar 26, 10:42 am, Steven wrote:
>> On Friday, March 26, 2010, roger peppe wrote:


>> > On 26 March 2010 16:47, Ryanne Dolan wrote:
>> >> Roger,
>>
>> >> The current fxn is not part of the global environment. A fxn cannot tell who
>> >> called it
>>
>> > erm, runtime.Caller() seems to enable just that.
>>
>> > obviously some of the global environment is hidden by
>> > the runtime - it's still there though, and that's what recover uses.
>>
>> Something related I've been wondering, what about other deferred function calls?
>>
>> func f1() (success bool) {
>>   defer func() { success = recover() == nil }()
>>   defer f2()
>>   panic("panicking")
>>
>> }
>>
>> func f2() {
>>   defer func() { _ = recover() }
>>
>> }
>>
>> func f3() { _ = recover() }
>>
>> Does f1() return true or false? I'd expect false, since it wouldn't
>> make much sense for a panic to be caught several levels up the call
>> stack. But then, if you replace f2 with f3, I'd expect true, since f2
>> is at the same depth as a closure would be, so it it's a matter of
>> depth wrt the panicker, it should be equivalent. However, if it's
>> something that gets wrapped by the closure, I'd expect false.
>>
>>  Essentially, how does pancking propogate upward?
>

Mue

unread,
Mar 26, 2010, 5:59:20 PM3/26/10
to golang-nuts
Ah, thx, that's almost what I wrote above. Only that I've called
recover what you called defer_on_panic. But the idea is the same:
simply defer a func that is called when a panic happened. So I take
you as a supporter. *smile*

mue

On 26 Mrz., 21:25, Vish Subramanian <vish.subraman...@gmail.com>
wrote:

David Roundy

unread,
Mar 26, 2010, 7:22:45 PM3/26/10
to Mue, golang-nuts
I think an important aspect of this "not-quite-exception" mechanism is
that it *does* unify the ordinary deffer mechanism with the
"exception-catching" mechanism, since it realizes that when you want
to "defer" something, you usually want to do it even if there is a
panic (i.e. it's an important cleanup).

Most deferred functions probably *won't* call recover, since recover
is almost always the wrong thing to do. Usually, using recover is
wrong unless panic is being abused, or you're at a high level trying
to write code that is robust even in the presence of bugs. However,
using defer is very often the right thing to do, any time you really
want to clean something up (or flush it) regardless of whatever else
happens. In those rare cases where you have a cleanup that you don't
want to do in case of a panic (e.g. it might not be safe in a
panicking state... a call to os.RemoveAll(".") might be in this
category, to pick a stupid example), you could use recover to avoid
cleaning up in case of panic, but that's hopefully a rare scenario.

David

Uriel

unread,
Mar 27, 2010, 5:05:59 AM3/27/10
to David Roundy, Mue, golang-nuts
On Sat, Mar 27, 2010 at 12:22 AM, David Roundy
<rou...@physics.oregonstate.edu> wrote:
> I think an important aspect of this "not-quite-exception" mechanism is
> that it *does* unify the ordinary deffer mechanism with the
> "exception-catching" mechanism, since it realizes that when you want
> to "defer" something, you usually want to do it even if there is a
> panic (i.e. it's an important cleanup).
>
> Most deferred functions probably *won't* call recover, since recover
> is almost always the wrong thing to do.

Given how many people are asking for ways to make calling recover()
easier to use and more 'convenient' maybe it should be made *harder*
to use to avoid the abuse that will likely ensue.

We should remember something rob mentioned: most programs should not
contain more than one or two calls to recover, and I think in many
cases perhaps none at all (should a program like grep(1) ever call
recover()? probably not).

My biggest fear is that people will build libraries that force client
code to use recover to deal with perfectly normal errors. Maybe to
discourage this panic() should be renamed
THEWORLDISGOINGTOENDWEAREDOOMED() ;)

uriel

Peter Froehlich

unread,
Mar 27, 2010, 9:12:05 AM3/27/10
to Uriel, David Roundy, Mue, golang-nuts
On Sat, Mar 27, 2010 at 5:05 AM, Uriel <ur...@berlinblue.org> wrote:
> My biggest fear is that people will build libraries that force client
> code to use recover to deal with perfectly normal errors. Maybe to
> discourage this panic() should be renamed
> THEWORLDISGOINGTOENDWEAREDOOMED() ;)

I don't think naming it differently will work. But you could have the
compiler throw in delays at random places all over the code (yes, also
into code outside the "panic path") if it discovers a call to
panic/recover anywhere in a package. Now that's discouraging! :-D
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab

Hans Stimer

unread,
Mar 27, 2010, 2:14:38 PM3/27/10
to golang-nuts
>
> My biggest fear is that people will build libraries that force client
> code to use recover to deal with perfectly normal errors. Maybe to
> discourage this panic() should be renamed
> THEWORLDISGOINGTOENDWEAREDOOMED() ;)
>
> uriel


Why not a proposal for streamlining the error handling we do 99% of
the time? I'm not against the exception proposal, it just seems like a
lot of effort for what we are all hoping will be a fringe case. I also
share Uriel's fear that exceptions will become used for reporting
errors out of libraries. This is more likely if you don't make the
desired behavior the easiest behavior. I've seen this happen a number
of times before, exceptions get introduced to a language and pretty
soon they are being thrown in everywhere, and it becomes a nightmare
trying to figure out who can throw what so it becomes common to just
stick a catch at the top level and terminate.

Here is a set of basic criteria for judging an improved error system
* Easy to read
* Easy to write
* Easy to audit
* Efficient - runtime
* Lends itself to a good standard convention
* Reliable diagnostics - stack trace from selected point of detection


Below are some ideas. I'm not wedded to any of these, but I would like
to see more ideas on how the desired behavior can be much more
convenient than the hopefully-not-inevitable panics strewn throughout
code.


// handle the error; error is reserved word, can only take interfaces
of os.Error
i, error := foo()


// ignore error. Tilde is used for ignored errors so as to discourage
the practice :-) It also makes it explicit that an error is being
ignored.
// If you have ever spent time fixing other people's bugs then you'll
appreciate an easy grep for ignored errors.
i, ~ := foo()


i, error := foo()
error // equivalent of "if error != nil { return } ". Calls defered
routines as usual.


i, error := foo()
error "failed to read" // prints message and stack crawl before
returning


i, error := foo()
error os.ErrorString("failed to read") // sets new value for error
before returning


// execute statements in brackets, then defered routines, and then
returns
i, error := foo()
error {
bar()
}


// handle error, then continue
i, error := foo()
error {
bar()
continue // goto works here too
}


func foo() (int, os.Error) { return 2, os.NewError("failed") }
func bar() {
i, error := foo()
error // compile failure because bar doesn't declare an os.Error
return type
}


func foo() (int, os.Error) { return 2, os.NewError("failed") }
func bar() os.Error { // don't need to name os.Error -- it is always
"error"
i, error := foo()
error
}

Daniel Smith

unread,
Mar 27, 2010, 3:29:01 PM3/27/10
to Hans Stimer, golang-nuts
You triggered a thought in my mind. Here's another idea to ease the syntactic burden of constantly writing:

f, err := os.DoSomething()
if err != nil {return err}

I propose adding one new keyword, "trigger," which functions almost exactly like "func," with these restrictions:
1. It can only take one parameter
2. It can only be used inside a function (no methods or top level usages allowed)
3. It may not list any return parameters
4. It behaves like a normal closure, except that:
4a. Using the return statement causes the enclosing function to exit
4b. It is assumed to have the same return signature as the enclosing function

And most importantly:
5. Instead of passing its single parameter to it like a function call (e.g., myTrigger(foo)), its single parameter is passed with an = sign (e.g., myTrigger = foo)

This would allow the following extremely concise and convenient code:

func Example(obj *foo) (ret *foo, os.Error) {
    error := trigger(err os.Error) {
        if err != nil {
            return nil, err //this will exit the function Example. Deferred functions will still get called.
        }
    }
   
    ret, error = DoSomethingThatCouldFail(obj)
   
    ret, error = DoSomethingElseThatCouldFail(ret)
   
    ret, error = DoSomeOtherThingThatCouldFail(ret)
   
    return ret
}

If your needs are more complex, you would write something like:

func FatalError(err os.Error) bool {
    if err == nil {
        return false
    }
   
    //do lots of involved stuff to see how bad it is, log it, whatever
   
    return true //yes, propagate the error further
}

func Example2(obj *foo) (ret *foo, os.Error) {
    error := trigger(err os.Error) {
        if FatalError(err) {
            return nil, err //this will exit the function Example2. Deferred items will still get called.
        }
    }

    ret, error = DoSomethingThatCouldFail(obj)

    ret, error = DoSomethingElseThatCouldFail(ret)

    ret, error = DoSomeOtherThingThatCouldFail(ret)

    return ret
}

I think this syntax is very go-ish, easy, will prevent people from using panic for normal errors, and will encourage people to use multiple return values as they should. And it will keep my code from looking like "if err != nil {return err}" salad.

I'm not sure "trigger" is the best word, but it seems to work well enough. The central idea is to set up an object that executes some code when you use the = sign on it. I would *love* to see that worked into the language somehow.



--
Daniel Smith
http://www.schaumburggoclub.org/

Corey Thomasson

unread,
Mar 27, 2010, 4:03:59 PM3/27/10
to golan...@googlegroups.com
---------- Forwarded message ----------
From: Corey Thomasson <cthom...@gmail.com>
Date: Saturday, March 27, 2010
Subject: Re: [go-nuts] Re: Proposal for an exception-like mechanism
To: Daniel Smith <luken...@gmail.com>


I feel like this makes code incredibly less readable. And does the
kind of action at a distance that not using panic tries to avoid