Proposal for an exception-like mechanism

3,575 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

Daniel Smith

unread,
Mar 27, 2010, 4:05:49 PM3/27/10
to Corey Thomasson, golan...@googlegroups.com
There's a major difference, though-- with panic, you never know if what you just called can or will panic. This way, you set everything up right there in the function. So you decide what to do with an error, not the function you called. And of course, you could call the "error" variable something else to make it more obvious like "TerminateIfError," if you wanted.

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.

Ostsol

unread,
Mar 27, 2010, 4:24:20 PM3/27/10
to golang-nuts
On Mar 27, 3: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() ;)
>
> uriel

I'm reminded of Python generators, which aren't even a library, but a
feature of the language -- and they indicate their end via an
exception (StopIteration).

-Daniel

Hans Stimer

unread,
Mar 27, 2010, 4:27:07 PM3/27/10
to golang-nuts
I like how it goes from 1 line in my proposal to 0 lines in yours.
Very nice. I kind of prefer putting the error handling code next to
the place where it is detected, but I could live with this.

Just to clarify, the minimum error code we have today is 3 lines, if
you use gofmt, which you should.


if err != nil {
return
}

Norman Yarvin

unread,
Mar 27, 2010, 6:00:17 PM3/27/10
to Rob 'Commander' Pike, Eleanor McHugh, golang-nuts
On Fri, Mar 26, 2010 at 10:08:55AM -0700, Rob 'Commander' Pike wrote:

> 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.

The sort of situation where I'd be tempted to discriminate different
kinds of panics is in writing a subunit of a program, say several
thousand lines of code with function calls nesting up to ten deep, in
which there are various weird errors that can happen due to program bugs
and/or input that is malformed in a way that I hadn't considered. I've
written such codes using only errors passed back explicitly; and it got
quite tiresome to write stuff like

if(error != 0)
return;

after a large percentage of the function calls, just in order to pass on
those errors to a higher level. Since this was a subroutine that could
be called by a variety of programs, I didn't want to just have it print
an error message and terminate execution. Nor, implementing that sort of
thing in Go, would I want to stomp on anyone else's use of the panic
mechanism. Panics caused by memory allocation failures, for instance,
I'd just want to pass through to the calling program, which has more idea
what is going on with memory than I do; likewise for other panics not
caused by calling panic() in my own code. The proposed mechanism seems
to support this admirably: I could just define a new type for my panics:

type fooPanic string

then call panic with an argument of that type, wherever I hit one of
those weird errors:

panic(fooPanic("Error: SUCKMUD. Shut her down Scotty, she's sucking mud."))

and in the top-level functions in my code, which other people will be
calling, recover from only those panics, and return the error message as
an ordinary string:

func Foo() (r ComplicatedObject, error string) {


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

if(reflect.Typeof(x) == reflect.Typeof(fooPanic(""))) {
r = nil
error = x.(fooPanic)
} else {
panic(x)
}
}
}()
...
}

This usage, in which the fact that panic() was used is invisible to the
rest of the program, seems about the polar opposite of obscenities like
having opening a file return an exception. (Due to the ease of returning
multiple values in Go, people will be less tempted to indulge in such
obscenities anyway, so one doesn't have to be too worried about them.)

In a huge, sprawling program of hundreds of thousands of lines, this sort
of usage might lead to having tens or hundreds of different
locally-defined panics. But it doesn't seem like that would cause any
problems.


--
Norman Yarvin http://yarchive.net

Daniel Smith

unread,
Mar 27, 2010, 6:15:22 PM3/27/10
to Norman Yarvin, Rob 'Commander' Pike, Eleanor McHugh, golang-nuts
Isn't that what they're trying to keep people from doing with panic, though? It might be easy for someone new to that codebase to accidentally call a function that could panic without realizing it.

If the cost of propagating an error up the call stack were reduced to only a few lines of code at the top of each function (as in my proposal a few emails ago), would you still want to use panic to accomplish this?

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.

Steven

unread,
Mar 27, 2010, 7:13:04 PM3/27/10
to Daniel Smith, Hans Stimer, golang-nuts
Couldn't you do this with an extra line, under the current proposal?

func checkError(err os.Error) {
if err != nil {
panic(err)
}
}

func Example(obj *foo) (ret *foo, err os.Error) {
defer func() {
if err != nil {
_ = recover()
// anything you want to do on error
ret = nil
}
}()

ret, err = DoSomethingThatCouldFail(obj)
checkError(err)

ret, err = DoSomethingElseThatCouldFail(ret)
checkError(err)

ret, err = DoSomeOtherThingThatCouldFail(ret)
checkError(err)

return
}

(I hear cries of abuse :)
The only way for a called function to return from its parent, it seems, is a caught panic. The thing is that in reality, there's a good chance you might do something different depending on which function returns an error.

In a way, I find the triggers intriguing, but at the same time, I find them appalling. 

Daniel Smith

unread,
Mar 27, 2010, 8:41:56 PM3/27/10
to Steven, Hans Stimer, golang-nuts
On Sat, Mar 27, 2010 at 6:13 PM, Steven <stev...@gmail.com> wrote:

(I hear cries of abuse :)
The only way for a called function to return from its parent, it seems, is a caught panic. The thing is that in reality, there's a good chance you might do something different depending on which function returns an error.

In a way, I find the triggers intriguing, but at the same time, I find them appalling. 

Well, you wouldn't be obligated to use them for every error condition, of course. They mostly help in the case when you're trying to handle the same set of errors repeatedly. For example, I have some functions that write some data structures to a file and make a dozen calls to binary.Write(). The three lines of "if err != nil {return err}" which must follow every call get tedious very quickly-- there's more lines handling errors than there are doing work. That runs counter to the philosophy of go, as far as I understand it.

My system would let you do more work than just "return err" in response to an error. But if you're doing enough work in your triggers that it makes your code hard to follow, you should probably just write code in the body of your function to handle the errors in the current way.

I.e., you shouldn't use a trigger to change something and then retry something that failed.

(One case in which additional code in a trigger might be acceptable is a situation where you need to do extra cleanup for a failure exit. You can't put it in a defer because you don't want it to be executed if everything succeeded.)

If you have errors coming from different sources (say, os, and some library you wrote) and you need to check for different errors from each, it's probably clearer to define separate triggers instead of making a big switch statement in one trigger. For example:

func Example(key1, key2, key3 string) (ret *foo, os.Error) {
    HandleOSErr := trigger(err os.Error) {
        if err != nil {
            return nil, os.NewError("Example failed: " + err.String())
        }
    }
    HandleMapErr := trigger(exists bool) {
        if !exists {
            return nil, os.NewError("Example failed, key not found")
        }
    }
   
    obj1, HandleMapErr := myGlobalMap[key1]
    obj2, HandleMapErr := myGlobalMap[key2]
    obj3, HandleMapErr := myGlobalMap[key3]

    ret, HandleOSErr = os.CombineSomehow(obj1, obj2, obj3)
    ret, HandleOSErr = os.DoSomething(ret)
    ret, HandleOSErr = os.DoSomethingAgain(ret)

    return ret
}

(Yes, I realize you couldn't have combined those two triggers anyway (without doing something horrible with interface {}), but you get the idea.)

Triggers save the above code 8-14 lines (depending on how liberal you are with newlines), but more importantly, the flow of the actual work of the function is not constantly interrupted, leaving it much more readable.

To answer your central point: Yes, if you make ten calls and want to handle errors from each of them differently, you shoudn't use triggers (it'd be silly to set up ten different triggers that would each be used once). But in that case error handling is arguably at least half the point of your code anyway, so I wouldn't find it nearly as much of an obstruction to readability.

Norman Yarvin

unread,
Mar 27, 2010, 8:57:10 PM3/27/10
to Daniel Smith, Rob 'Commander' Pike, Eleanor McHugh, golang-nuts
On Sat, Mar 27, 2010 at 05:15:22PM -0500, Daniel Smith wrote:
>Isn't that what they're trying to keep people from doing with panic, though?

I'm not entirely sure. On the one hand, these are genuinely rare errors
that I'm talking about: they might not ever get triggered during the
entire life of the code. On the other hand, what I suggested does go
against some of the more extreme statements made about discouraging use
of panic.

>It might be easy for someone new to that codebase to accidentally call a
>function that could panic without realizing it.

Unless they were adding a new externally-visible function to the code,
that wouldn't matter. If they were adding a new externally-visible
function, they'd likely start by copying one of the existing ones, and
thus copying the panic-recovery code. If they didn't do that, the worst
that'd happen would be a panic leaking out, and the program crashing
where it might have recovered.

>If the cost of propagating an error up the call stack were reduced to only a
>few lines of code at the top of each function (as in my proposal a few
>emails ago), would you still want to use panic to accomplish this?

The mechanism you proposed -- making magic variables which call a
function when something is assigned to them -- is too much like a COME
FROM statment for my taste. (COME FROM certain assignment statements...)
Besides being confusing, I think it'd be hard to compile. Also, if the
errors you are dealing with are at all likely, you should handle them in
more detail than just doing the same thing no matter which function
returned an error; or if they're rare, then that's what panic() is for.

Giles Lean

unread,
Mar 27, 2010, 9:42:02 PM3/27/10
to Norman Yarvin, golang-nuts

Hi Norman,

I'm going to disagree with almost all of your well writen and
well reasoned (although wrong thinking IMHO :-( ) posting.

Because it's so clear and specific yours is one of the easier
postings to respond to: I've been mulling a more general
response I hope to post later today.

Plese don't take anything here personally: this an unclear
technical area (what is "best practice" for exceptions or
Rob's proposed panic("these are not exceptions") is not yet
established) and is thus an area where legitiately different
viewpoints can be held. It's not politics, or religion, or
which football code you support. :-)

Norman Yarvin <yar...@yarchive.net> wrote:

> The sort of situation where I'd be tempted to discriminate
> different kinds of panics is in writing a subunit of a
> program, say several thousand lines of code with function
> calls nesting up to ten deep, in which there are various
> weird errors that can happen due to program bugs and/or
> input that is malformed in a way that I hadn't considered.
> I've written such codes using only errors passed back
> explicitly; and it got quite tiresome to write stuff like
>
> if(error != 0)
> return;
>
> after a large percentage of the function calls, just in
> order to pass on those errors to a higher level.

So? It's an error. It's not a shocking, out of this world
event: it's just an error. So return the error. I doubt
you'd argue -- as some ex-coworkers have, that it _takes too
long to type_, because clearly typing a few lines of pretty
much boilerplate code doesn't take a lot of your coding
(design, review, redesign, document, code, review, fix, test,
etc) time in total.

Personally, as I type those return lines I take the time to
quickly ask myself if I should be passing the error up, and
endeavouring to recall what the higher level (if it's written
yet) will do with the error. This sometimes picks up
incompatible assumptions ("I thought you looked after that")
or design inelegancies if seldom design errors. So (again
IMHO) even the typing time needn't be wasted.

Other coworkers have complained that returning errors this way
makes their code too long to _read_; my response to that is
that I don't trust their code until I see how it handles
errors, so they might as well get on with it. This worked
somewhat when the coworkers concerned were junior to me, and
not very well otherwise! No surprise there. :(

> Since this was a subroutine that could be called by a
> variety of programs, I didn't want to just have it print an
> error message and terminate execution.

Naturally not, so you returned the error upward, and the
caller (and callers in future programs) could determine what
was best *for them*.

When an error happens in low level code all you have to do is
report that it happened. Worry about recovery v. bailing out
at higher levels. (That's one of the nice things about lower
level code which Go makes harder: now it's necessary to give
more thought to the specific error that is passed back. As an
example, in some code I've had to reach into the syscall
package because the os package pushed errno into a string,
which wasn't helpful when only some errno values were
problematic for my calling code.)

> Nor, implementing that sort of thing in Go, would I want to
> stomp on anyone else's use of the panic mechanism. Panics
> caused by memory allocation failures, for instance, I'd just
> want to pass through to the calling program, which has more
> idea what is going on with memory than I do; likewise for
> other panics not caused by calling panic() in my own code.
> The proposed mechanism seems to support this admirably: I
> could just define a new type for my panics:

I'll like to stop here: your panics shouldn't be panics. But
I can't, quite; two more paragraphs needs addressing.

If you have something that can be construed as an error, it
should be treated as one. Not any flavour of panic (local to
your code, local to your site, local to the way you think
about it): just an error.

At the momemnt, I believe that panic() should be reserved for
the painted-into-the-corner, no way out, no way to continue
situations, and that's all.

Exceptions have been promoted as coding convenience. History
says in that situation they've mostly been abused. Hence (I
think -- I don't claim to speak for Rob) Rob's proposal of
something more restricted, which I am not wanting to see
turned into yet another flavour of exception which infiltrates
into every package out there and renders Go more difficult to
use than it should be.

We understand error returns and their handling. Yeah, it's
not pretty. Sometimes the code is a little cluttered. But
(absent programmers who don't check the errors, and with
documentation of what errors to look for) it can be pretty
reliable.

> This usage, in which the fact that panic() was used is
> invisible to the rest of the program, seems about the polar
> opposite of obscenities like having opening a file return an
> exception.

No it isn't; it't _exactly_ the same thing, merely in a more
modest manifestation. You're turning a normal-but-undesirable
outcome from a mere error to something that permutes the
application's cotrol flow and thus becomes action at a
distance and increases troubleshooting difficulty.

The only thing I can see in favour of your idea is that it
immediately identifies the subsystem with the problem, so that
if it's not your subsystem, you can hand the problem off to
someone else. Some internal logging from within the low level
code would also do that, without the additional costs.

> (Due to the ease of returning multiple values in Go, people
> will be less tempted to indulge in such obscenities anyway,

> ...)

That much I concur with. :-)

> In a huge, sprawling program of hundreds of thousands of
> lines, this sort of usage might lead to having tens or
> hundreds of different locally-defined panics. But it
> doesn't seem like that would cause any problems.

I beg to differ. I want error returns as error returns, and
panics as true panics, not just stuff that's "awkward to deal
with".

Programming is about the "awkward to deal with" details; we
don't have a silver bullet yet.

Best regards,

Giles

P.S. Cricket, not football of any code. :-)

Lawrence Bakst

unread,
Mar 27, 2010, 10:09:26 PM3/27/10
to Rob 'Commander' Pike, golang-nuts Nuts
On Thu, Mar 25, 2010 at 2:36 AM, Rob 'Commander' Pike <r...@google.com> wrote:

> Here is a simple example.
>
> func f(dopanic bool) {

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

> fmt.Printf("panicking with value %v\n", v)

That should be:
fmt.Printf("panicking with value %v\n", x)
correct?
--
~leb

Daniel T

unread,
Mar 27, 2010, 10:23:39 PM3/27/10
to golang-nuts
Giles,

I very much agree with your posting. It's hard for me to think of a
situation where I would want to issue a panic outside the runtime.
And then using recover would only be a means to recover despite
program errors in a module and rare hardware failure. If an "error"
can be defined and anticipated ahead of time, it should not be a panic
(it should be a normal error).

What if you are only allowed to issue a panic statement inside unsafe
code or inside the runtime?
You can still check for recover anywhere.

Any thoughts of limiting where panic can be issued?

-Daniel

Rob 'Commander' Pike

unread,
Mar 27, 2010, 11:50:13 PM3/27/10
to Lawrence Bakst, golang-nuts Nuts

Of course.

-rob


Steven

unread,
Mar 28, 2010, 12:02:09 AM3/28/10
to Daniel T, golang-nuts
On Sat, Mar 27, 2010 at 10:23 PM, Daniel T <kard...@gmail.com> wrote:
Giles,

I very much agree with your posting.  It's hard for me to think of a
situation where I would want to issue a panic outside the runtime.
And then using recover would only be a means to recover despite
program errors in a module and rare hardware failure.  If an "error"
can be defined and anticipated ahead of time, it should not be a panic
(it should be a normal error).

What if you are only allowed to issue a panic statement inside unsafe
code or inside the runtime?
You can still check for recover anywhere.

Any thoughts of limiting where panic can be issued?

-Daniel

What if recover only worked when the panic was issued within the same package? That would make it impossible to design a library around it, but then, you wouldn't be able to protect yourself from unreliable code (which could be a good thing anyway, since it would limit the acceptance of unreliable libraries that don't handle their own errors).

The problem I have with the triggers is that they're syntactically inconsistent and don't do what you'd expect (don't get a value assigned to them, but rather use it). And if you're calling the same function repeatedly so as to be doing the same thing in case of an error each time, you could wrap the function in a closure and put your error handling logic there. The only problem seems to be that you can't return from the enclosing function (except, in the case of this proposal with a panic/recover).

Norman Yarvin

unread,
Mar 28, 2010, 1:36:59 AM3/28/10
to Giles Lean, golang-nuts
On Sun, Mar 28, 2010 at 12:42:02PM +1100, Giles Lean wrote:

>Norman Yarvin <yar...@yarchive.net> wrote:
>
>> The sort of situation where I'd be tempted to discriminate
>> different kinds of panics is in writing a subunit of a
>> program, say several thousand lines of code with function
>> calls nesting up to ten deep, in which there are various
>> weird errors that can happen due to program bugs and/or
>> input that is malformed in a way that I hadn't considered.

...

>So? It's an error. It's not a shocking, out of this world
>event: it's just an error.

Most of the things I had in mind were pretty clearly bugs. Their
implication was that this whole run of the algorithm was toast: there was
no point in continuing it. Even if it were some malformed input that
triggered the bug, there'd be no point in continuing. (In that case, the
bug is not that the algorithm failed; the bug is that I report a bug,
rather than telling the user exactly how his input is malformed.)
Indeed, the only point in returning an error at all (as opposed to just
terminating the whole program immediately) was that a larger program in
which this was embedded might be able to save some other part of the data
it was manipulating. For instance a CAD program, in which someone had
labored for an hour designing something, might catch the error code I
returned, tell the user that whatever he just tried didn't work because
of "unknown error #67" (or whatever I was calling it), and let him save
his design to a file. Other programs might be able to do something else
to partially recover. But as far as my algorithm was concerned, these
errors meant that it was all over -- might as well go directly to the
exit. Which is exactly what I had in mind doing via panic().

> So return the error. I doubt
>you'd argue -- as some ex-coworkers have, that it _takes too
>long to type_, because clearly typing a few lines of pretty
>much boilerplate code doesn't take a lot of your coding
>(design, review, redesign, document, code, review, fix, test,
>etc) time in total.

It added enough time for me to get mighty tired of it.

>Personally, as I type those return lines I take the time to
>quickly ask myself if I should be passing the error up, and
>endeavouring to recall what the higher level (if it's written
>yet) will do with the error. This sometimes picks up
>incompatible assumptions ("I thought you looked after that")
>or design inelegancies if seldom design errors. So (again
>IMHO) even the typing time needn't be wasted.

In this case it was wasted; I knew before typing that all these errors
needed to go straight to the top.

Returning errors one step at a time, and examining them at each step, is
often worthwhile. That's because each level sees the error in a
different context, and can apply a different judgement to it and respond
to it in different ways. The error can often usefully be relabeled as it
progresses up, in a way that makes it more meaningful. What one level
sees as failure to read from file descriptor X, at a higher level might
be recast as a loss of connection to the SQL server.

But those weren't the sorts of errors I had in mind using panic() for.

Giles Lean

unread,
Mar 28, 2010, 2:04:04 AM3/28/10
to Daniel T, golang-nuts

Daniel T <kard...@gmail.com> wrote:

> What if you are only allowed to issue a panic statement inside unsafe
> code or inside the runtime?
> You can still check for recover anywhere.

I was thinking late last night: should panics be able to cross
packages; should recover() only be able to be issued in
main(), or package main, or in the first x level (for some
small x) level of calls within a package ... and each time I
found either:

a) a seemingly valid use case
b) some way to abuse panic()/refer() anyway

> Any thoughts of limiting where panic can be issued?

No. Anticipating the more general posting that I might not
now get around to making:

o there has been a background rumble from the time of Go's
introduction saying "we want exceptions"

o from Rob's postings, there appears to have been a
prospective decison and prospective design prior to his
initial posting.

I'm not suggesting that input was not wanted, or even that
the proposal couldn't have been shot down, but that some
pretty serious work went into thinking about whether to have
the feature and what it should look like before Rob's first
post.

o Ian Lance Taylor's postings definitely swayed me; I can't
argue in the face of his writing that Go without
panic()/recover() would be a stronger language than Go with
the feature.

The (fairly typical these days) pop-up GUI when something
crashes offering to send off a problem report is something
that would be made easier just as one example. (I think now
you'd need to use a minder process that tried to catch the
stack trace ... not sure, haven't tried.)

o discussion since Rob's initial posting hasn't knocked holes
in Rob's design -- there have been a couple of
clarifications, but that's about it so far as I see

(The complaints about the syntax I discount to a degree;
while syntax matters it's the semantics that are my main
interest.)

o there have been a couple of proposals for making the use
of panic()/refer() _harder_ ... but in a world where
people code in Fortran, C++, and x86 assembler I don't
think there's any syntax that can prevent irresponsible
use of any feature!

o there has been some positive discussion of making error
handling easier (I do agree if err != nil { ...} isn't
scintillating to spend time writing: if those ideas lead to
something it would be good)

o there has been more discussion than I've cared for of how
Rob's panic("these are not exceptions") really can be used
in ways similar to exceptions in other languages to pervert
control flow

o there has been discussion (e.g. Norman Yavin's post) that
has shown no reluctance at all to head right on in to any
panic()/recover() feature and use it in ways that newcomers
to his projects will likely find "new and interesting"

(Sorry Norman: again, nothing personal: yours was just a
very clear posting with more detail than most about what you
might do, and thus a good example for criticism by those of
us who hold different opinions to yours.)

o there's been little discussion of _when_ to use
panic()/recover() v. when to maintain error returns

o I haven't seen a single example of "Great! That's perfect
for XYZ where we are currently having to do ABC, and that
sucks because ...".

The closest we've come (that I recall, anyrate) are vague
references to memory allocation failures and Ian's hoped for
divide by zero panic, but these were both generic ideas, not
concrete references to existing code.

I want to read Rob's postings and the thread again
(fortunately I'm in Oz, so my Monday morning is still the
weekend in the USA :-), but from where I sit this minute:

a) I expect we'll get the feature (indeed I wouldn't be
/entirely/ surprisd if Rob hasn't played with an
implementation or two before writing his proposal)

b) we'll have to try hard to develop a culture of using it
well, so that libraries from different sources will be able
to "play nice" together

c) there will be some horrible examples where the feature is
abused. I suppose this goes without saying, and is true
for any technological development, and shouldn't stand in
the way of progress. :-/

d) it's possible (I'd like to think not, but it might be)
that for Go to become a mainstream lanaguage, it needs
panic() and defer().

I suppose I am now in the camp where I'd like to see the
feature introduced.

I'd still not (NOT NOT NOT!) like to see the typical library
and program both have calls to panic(): if that happens, Go
for me will lose a lot of its charm.

After dabbling with Java and working with C++ I returned to C
as much as possible: Go has real possibilities for being a
most-of-the-time C replacement for me. Whether it can displace
perl for quick hacks and programming when I need the program
yesterday will take a while to figure out.

Regards,

Giles
--

chris dollin

unread,
Mar 28, 2010, 3:01:35 AM3/28/10
to Giles Lean, Daniel T, golang-nuts
On 28 March 2010 07:04, Giles Lean <giles...@pobox.com> wrote:

o there has been some positive discussion of making error
 handling easier (I do agree if err != nil { ...} isn't
 scintillating to spend time writing: if those ideas lead to
 something it would be good)

I wondered about a construct of the form

  try y := f(x)

f is expected to return two results. The first is assigned to y. The second
is an error value. If it is non-nil, then the executing function returns.
The error value is assigned to its final result variable; any other results
have their zero values. Thus this construct is shorthand for

  y, err := f(x); if err != nil { return zero1, zero2, ..., err }

and captures a purely function-local notion of error detection and
forwarding.

Expansion:

* f could return /at least one/ result, and the assignment list y
  would have one fewer variable than that. The case where f
  has only one result would be written `try f(x)`.

* The comma-ok idiom uses bool values, not might-be-nil
  values. So f's final result should be permitted to be a
  boolean and the test should be for being false:

  y, ok := f(x); if !ok { return zeroes, false }

* If f has named return variables, they probably shouldn't be
  smashed with zeros but left alone. Rather than

    return zeroes, errorvalue

  it's probably better expressed as

    errorvariable = errorvalue; return

* the case where the error result from this function doesn't
  have a type suitable for the error result from f isn't
  handled. I'm not sure it can be without adding complication
  that makes using the longhand just as simple. My off-the-cuff
  suggestion would be

    try y := f(x) SUITABLEPUNCTUATIONORKEYWORD errf

  where errf is a `func (errortypeof f) errortypeofthisfunction`
  (and I'm expecting it to be a named function).

* I picked `try` because it already has (from Java et al)
  error-handling connotations, but is so conspicuously
  /not/ something like try in et al that it shouldn't be
  confusing (ha). I'm not too fussy about the spelling.

Chris

--
Chris "allusive" Dollin

Giles Lean

unread,
Mar 28, 2010, 3:11:44 AM3/28/10
to Steven, Daniel T, golang-nuts

Steven <stev...@gmail.com> wrote:

> What if recover only worked when the panic was issued within
> the same package?

I thought about it: but I see too many situations where you
want to throw your hands up in horror, get to the top level of
the current goroutine, send a message on a channel to your
controlling goroutine if your application has one, wrap
everything up, and exit, but more gracefully than just calling
os.Exit().

If you can't do that much, panic() and recover() would look
very limited indeed.

Plus, anyone who found the rule too awkward would put their
whole application into the 'main' package. :-)

[ No comments on the triggers; I'm interested, but haven't though
about them. ]

Giles
--

Giles Lean

unread,
Mar 28, 2010, 3:11:42 AM3/28/10
to Daniel T, golang-nuts

Daniel T <kard...@gmail.com> wrote:

> I very much agree with your posting. It's hard for me to think of a
> situation where I would want to issue a panic outside the runtime.

Easy, two cases, #1 and #2.

#1 (not compiled, forgive any errors please):

var c [256]byte = ...

func xyzzy(a []byte) (n int, err os.Err) {
if len(a) > len(c) {
return ...
}

switch {
case len(a) < 9:
...
case len(a) < 99:
...
case len(a) <= 256:
...
defult:
// can't happen due to guard at top
panic(...)
}
return
}

Obviously this example is contrived; in real life the initial
gaurd clause wouldn't be there and you'd assume bad input in
the switch default. But for pedagogical purposes, let's
assume the guard is there and continue on.

There're a couple of schools of thought here:

1. don't have the default case, it can't happen, save the code

2. put in the default case, as all switches should have
default cases (we all know how easy it is to miss a case,
either originally or later during maintenance and
enhancement) ... but then you must worry about what to put
in it.

Here, if the code in the default block is ever hit you know
that the length of 'a' changed under your feet between the
guard clause and the switch. That's technically known as a
Bad Thing.

Now, if there are no other goroutines that could touch the
array underlying the slice, I'd be looking for (roughly in
order of likelihood):

a) a wild pointer from some unsafe code in another goroutine
b) a wild pointer from some cgo code in another goroutine
c) faulty memory
d) a faulty CPU
e) other faulty hardware (e.g. broken DMA engine, bad disk
or controller and you got paged out and back in)
f) a compiler or runtime bug
g) an OS bug (or 'c', 'd' or 'e') during a context switch
into the kernel

Depending on your confidence in the compiler you could move
'f' up a bit, but in real life (admittedly in support,
where you get to see all the fun disasters) I've seen the C
language equivalent of all but 'g' cause application
corruption, and I've escaped 'g' probably because I've
mostly worked on a non-x86 platform where user memory
wasn't mapped into kernel space and kernel programmers had
to work hard to touch user memory.

I've seen the C language equivalent of all of those also
cause OS crashes or panics, and if they're valid grounds
for an OS panic then I think they're valid grounds for an
application panic too, but just like with OS crashes (where
we risk more damage by writing stack traces, crash dumps,
etc) it's typically worth the risk in an application even
with corrupted memory to try to log some specifics of what
happened, so having refer() to temporarily grab control
would be nice.

This, I don't think, is the sort of situation too many of the
posters talking about using panic() have had in mind. My
apologies of course if am making a misjudgemnt here, and let
me know (gently, for preference, but the secret that I've
worked in support and have a thick skin is out now, so let
fly if you have to).

#2:

Obviously as well as #1 above any sufficiently impossible
application state might cause a call to panic(): what states
will be permitted to do that will be application and situation
dependent.

Mission critical applications will have different standards to
quick-and-dirty adjunct applications where you choose to bet
that a situation will never occur in real life and toss in a
panic to catch it at least slightly gracefully if it does.

None of the above is about not returning errors when all you
have is an error -- out of bounds user input, a corrupt file
off disk, a broken TCP connection, a full disk, and all the
myriad ordinary things that happen that we as programmers
would prefer not to have to worry about.

Further, the more general you package and the more widely it
will be used, the greater (IMHO) responsibility to avoid
calls to panic().

Which is probably enough from me: unless I have very specific
comments on portions of Rob's proposal, I'll try to be quiet
for a while!

Best regards,

Giles
--

Giles Lean

unread,
Mar 28, 2010, 3:51:22 AM3/28/10
to Norman Yarvin, golang-nuts

Norman Yarvin <yar...@yarchive.net> wrote:

> Most of the things I had in mind were pretty clearly bugs. Their
> implication was that this whole run of the algorithm was toast: there was
> no point in continuing it.

> ...


> Indeed, the only point in returning an error at all (as opposed to just
> terminating the whole program immediately) was that a larger program in
> which this was embedded might be able to save some other part of the data
> it was manipulating.

Thanks! I hadn't understood you properly. That kinda-matches
one of the use cases I talked about in my last email (which
undoubtably crossed with yours).

So much as my 2c is worth, this is what a panic() and
recover() facility would indeed be helpful for: as graceful as
possible application degradation on the way to restart when
it's SNAFU time.

Somewhat like the Mac's:

"You need to restart your computer. Hold the Power button
for several seconds or press the Restart button."

is at least polite-ish, compared to the old Windows Blue
Screen of Death. It's still _bad_, but it tries to help the
user through the experience rather than have them wonder what
they've done wrong.

(Mind you, the ranting the first time someone somewhat
technically competant had that message appear on his
brand-spanking-new unibody MacBook Pro on a cruise in
Antarctica was something to behold. He didn't believe Macs
did that. We laughed at him, kind and experienced people that
we were.)

Let's hope we see some more good use cases develop, so that
that "Effecive Go" document can know about them as soon as the
feature hits the language.

Bearing in mind the ... unfortunate ... nature of many
languages' use (or those languages' programmers' use) of
exceptions, the educational process will be a challenge.
Starting with panic("these are not exceptions"). :-)

(Rob needs a trade mark on that. And he probably had the
tshirt made this weekend.)

Thanks again Norman for responding and elaborating.

Best ragards,

Giles

Eleanor McHugh

unread,
Mar 28, 2010, 11:11:39 AM3/28/10
to golang-nuts
> 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).

Does this only apply to deferred functions?


Ellie

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

Russ Cox

unread,
Mar 28, 2010, 11:31:06 AM3/28/10
to Eleanor McHugh, golang-nuts
> Does this only apply to deferred functions?

No. Any closure can refer to return values now, at tip.
The change will be in the next release.

Russ

Esko Luontola

unread,
Mar 28, 2010, 4:15:39 PM3/28/10
to golang-nuts
On Mar 28, 9:04 am, Giles Lean <giles.l...@pobox.com> wrote:
> o I haven't seen a single example of "Great!  That's perfect
>   for XYZ where we are currently having to do ABC, and that
>   sucks because ...".
>
>   The closest we've come (that I recall, anyrate) are vague
>   references to memory allocation failures and Ian's hoped for
>   divide by zero panic, but these were both generic ideas, not
>   concrete references to existing code.

I'll mention one use case:

I'm making a testing framework and nowadays if the code being tested
panics (which is quite common in code under development, especially
test-first development; null pointers, invalid array indexes etc.
happen often), the execution of the whole test suite fails. This has
the negative effect that no other test failures can be reported, not
even assertion failures in the same test, which makes it harder for
the developer to identify the reason for the failure (seeing patterns
of failing tests can often pinpoint that which component or even which
line has the bug). Another negative effect is that a full goroutine
dump is not a very user friendly error message, because the framework
will have hundreds of goroutines running concurrently (one goroutine
per test), which will result in the original reason for failure (the
first goroutine in the goroutine dump) to quickly scroll past the
terminal's scrollback buffer. Also the framework will not be able to
report things like what was the name of the failing test (although you
can get that information by following the stack trace line numbers).

Some way to catch panics within a goroutine or a function is needed
for the testing framework to handle panics with better usability.
Another requirement is the ability to programmatically get a stack
trace of the panic, so that the testing framework can report it as it
wishes.

The current proposal appears to satisfy this need. However, this is
not a very typical use case (a platform typically has only a few
testing frameworks), so it's not of much use in evaluating alternative
approaches to an exception-like mechanism. Anything that satisfies the
two requirements mentioned in the previous paragraph (catch panics &
programmatic access to stack traces) will be able to meet these needs,
regardless of how complicated the feature is to use, because the panic
handling code needs to be written only once.

Russ Cox

unread,
Mar 28, 2010, 4:48:17 PM3/28/10
to Esko Luontola, golang-nuts
Here are three concrete examples.

(1) It would be nice if in package http,
a single panicking handler on one connection
hung up on that connection but didn't cause
the whole server to fall over.

(2) There is code now that simulates panic+recover
by starting new code in a goroutine so that it can
do a non-local goto back to the original by sending
its result on a channel and exiting the goroutine.
This is how t.Fatal works in the testing library, for example.
It was also used in the template and regexp parsers,
to allow a recursive descent parser to exit the parse
easily. Unfortunately, this meant that templates and
regexps couldn't be parsed during initialization, so
they had to be rewritten with explicit checking,
which got quite a bit messier. It would be nice to
go back.

(3) The code below parses a specific structure from
a byte array, relying on the array bounds checks to
catch malformed input.

Russ


func UnmarshalDir(b []byte) (d *Dir, err os.Error) {
defer func() {
if v := recover(); v != nil {
err = ProtocolError("malformed Dir")
}
}

n, b := gbit16(b)
if int(n) != len(b) {
panic(1)
}

d.Type, b = gbit16(b)
d.Dev, b = gbit32(b)
d.Qid, b = gqid(b)
d.Mode, b = gperm(b)
d.Atime, b = gbit32(b)
d.Mtime, b = gbit32(b)
d.Length, b = gbit64(b)
d.Name, b = gstring(b)
d.Uid, b = gstring(b)
d.Gid, b = gstring(b)
d.Muid, b = gstring(b)

if len(b) != 0 {
panic(1)
}
return d, nil
}

func gbit8(b []byte) (uint8, []byte) {
return uint8(b[0]), b[1:]
}

func gbit16(b []byte) (uint16, []byte) {
return uint16(b[0]) | uint16(b[1])<<8, b[2:]
}

func gbit32(b []byte) (uint32, []byte) {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 |
uint32(b[3])<<24, b[4:]
}

...

Hans Stimer

unread,
Mar 28, 2010, 5:28:20 PM3/28/10
to golang-nuts
I think the code below proves the point that we need a streamlined
error system before exceptions. When writing error issuing/handling
code is laborious and tedious then exceptions start looking
attractive. Subsequently, array outbounds panics seems like a great
way to check for malformed input.

Russ Cox

unread,
Mar 28, 2010, 5:45:03 PM3/28/10
to Hans Stimer, golang-nuts
On Sun, Mar 28, 2010 at 14:28, Hans Stimer <hans....@gmail.com> wrote:
> I think the code below proves the point that we need a streamlined
> error system before exceptions. When writing error issuing/handling
> code is laborious and tedious then exceptions start looking
> attractive. Subsequently, array outbounds panics seems like a great
> way to check for malformed input.

What do you propose instead?
I admit that using the array bounds checks seems
a little sketchy, but I don't see a simpler way, even
if you imagine arbitrary additional language features.

Russ

Daniel T

unread,
Mar 28, 2010, 5:57:49 PM3/28/10
to golang-nuts
Wouldn't example #3 be the same as:
error :=func() {

err = ProtocolError("malformed Dir")
}
n, b := gbit16(b)
if int(n) != len(b) {
error()
}

-?-
Or does the panic-recover mech gain you something I'm not seeing...?

On Mar 28, 2:45 pm, Russ Cox <r...@golang.org> wrote:

Giles Lean

unread,
Mar 28, 2010, 7:38:22 PM3/28/10
to Esko Luontola, golang-nuts

Thanks Eric and Russ for providing additional use cases.

Esko Luontola <esko.l...@gmail.com> wrote:

> The current proposal appears to satisfy this need. However,

> this is not a very typical use case ...

Well, it's a _common_ use case, and any common real world use
case is helpful to measure the proposal against. But as you
say later:

> Anything that satisfies the two requirements mentioned in
> the previous paragraph (catch panics & programmatic access
> to stack traces) will be able to meet these needs,
> regardless of how complicated the feature is to use, because
> the panic handling code needs to be written only once.

Agree. It's like source code management: everyone uses it,
but few need to know the recipe to the secret sauce, or want
to. So all your use case is really concerned about is the
semantics, not the syntax.

Having maintained some #(*#$@)* awful test suites implemented
badly I think the syntax matters a bit: it'd be nice if it
were easy enough for a casual part-time programmer to
comprehend and use.

I make no sneer there at part-timers: I'm just aware how many
people get to put together code to help them do their job,
without the benefit of any computer science background. Such
people won't (can't) get efficient use out of C++, do better
with Python and sometimes Perl, and I've seen whole
applications (not web based ones) written in PHP because that
was what the author could write. *sob*

The syntax Rob describes isn't arduous, and having to face a
"deferred closure" whatchmacallit rather than a try ... catch
pair of keywords is a good Clue that these aren't the
droids^H^H^H^H^H^H^H exceptions you're looking for, for people
looking for features like that who do have computer science
backgrounds.

It's perhaps a bit advanced for the casual programming crowd,
but then they'll probably survive if it comes to it if their
test suite crashes on any error, and when they hit a critical
bug in their code their user gets a stack backtrace.

Bottom line for me: keeping panic()/defer() as other than a
strangely named exception handling mechanism is more important
than making it instantly comprehensible to the casual Go
pogrammer. If it's in the "advanced topics" chapter, well,
that's probably OK.

Reverting closer to the original topic:

What I think I have seen emerging in the discussion is:

a) a willingness by some people to _allow_ the feature to be
awkward to use if that will discourage it's abuse

b) discusion of the actual functionality's semantics, not
its syntax

If nobody's too worked up about the syntax and everyone is
concentrating on the semantics, then that seems like a good
thing and I'd leave the syntax decision to Rob and the rest
of the Go team.

Cheers,

Giles (darn, already Monday)

Asa Zernik

unread,
Mar 28, 2010, 8:05:50 PM3/28/10
to golang-nuts
I'm seeing a lot of discussion here about how nice it would be to use
panics to handle array bounds errors; that would be the optimal
solution for wrapping and handling unexpected runtime errors so that
they can be presented nicely to the user. But for the more general
case of avoiding the annoyance of bounds checks wouldn't it be more
idiomatic and consistent to copy the syntax for safe map lookup? I'm
thinking something like

if x, ok := arrayOrSlice[index]; ok { foo(x) } else
{ handleBoundsError() }

as shorthand for doing index >= 0 and index < len(arrayOrSlice)
checks. It's much less complication (and less typing!) than using
panic recovery to catch this kind of thing in a context-sensitive way,
and has the benefit of sticking the normal-case code out in front,
where a reader can see it and so understand the intended function of
the code, while shoving error handling out to the else-clause.
Extension of the "value, ok" pattern might even be useful for slice
expressions, and really any situation where formulaic checks are
required to avoid runtime exceptions.

Asa

On Mar 28, 2:45 pm, Russ Cox <r...@golang.org> wrote:

Russ Cox

unread,
Mar 28, 2010, 8:08:16 PM3/28/10
to Asa Zernik, golang-nuts
On Sun, Mar 28, 2010 at 17:05, Asa Zernik <asa...@gmail.com> wrote:
> I'm seeing a lot of discussion here about how nice it would be to use
> panics to handle array bounds errors;

This is only one of the three examples I posted.

Russ

Asa Zernik

unread,
Mar 28, 2010, 8:27:20 PM3/28/10
to golang-nuts
Oh absolutely; I was just talking about that one particular case, and
even on that one, going off on a tangent. I completely agree with you
on the usefulness of the panic/recovery mechanism in the cases you
gave.

Asa

On Mar 28, 5:08 pm, Russ Cox <r...@golang.org> wrote:

Giles Lean

unread,
Mar 28, 2010, 8:54:46 PM3/28/10
to Rob 'Commander' Pike, golang-nuts Nuts

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

> During panicking, if a deferred function invocation calls
> recover, recover returns the value passed to panic and stops
> the panicking.

Got that.

> At any other time, or inside functions called by the
> deferred call, recover returns nil.

Will this be enforced by the runtime (I think so) or the
compiler?

If it's enforced by the runtime, then I can have a function
that in some call paths is directly deferred, and in others
called from a deferred function and any recover() calls it
makes will be ignored.

Mind, I'm not sure I'd want to do this; I just suspect it
might be useful sometime (if a bit undisciplined, but urgent
maintenance sometimes has to cut corners) and am curious to
know if it will be possible or not.

As you can see I'm reduced to nitpicking questions. After
seeing a weekend's worth of postings go by (too many of them
mine) and reviewing your proposal and the ensuing thead again
this morning this is all I can come up with. I usually manage
to ding the target, but the target's not usually as
challenging as a Rob 'Commander' Pike proposal! (You realise
the frustration you've caused, right?? :-)

I think (after coffee) I'll go soothe my nerves opening some
new CL's. Which you can blame on Russ or someone, yes. <grin/>

Thanks,

Giles

P.S. At least with panic("these are not exceptions") I still
get to maintain my dislike of exceptions. "Oh, Go? No, it
doesn't have exceptions. You can call panic() and recover
from it sometimes, but functions aren't expected to throw
exceptions, none of that stuff." :-)
--

jimmy frasche

unread,
Mar 28, 2010, 9:42:04 PM3/28/10
to Giles Lean, golang-nuts Nuts
What happens if through some incompetence or accident one panics with
a nil value? Would the panic stop bubbling at recover but be
misreported as a non-panic? If so, in the static case the compiler
could issue an error but in some unfortunate dynamic case (such as,
the error codes are in a map and a computed key is not in the map thus
under the new rules returning a nil) I can imagine this being a very
bizarre debug session. Would func recover() (error interface{},
is_a_panic bool) be a safer type signature if the above holds?

Norman Yarvin

unread,
Mar 28, 2010, 10:42:54 PM3/28/10
to chris dollin, Giles Lean, Daniel T, golang-nuts
On Sun, Mar 28, 2010 at 08:01:35AM +0100, chris dollin wrote:
>On 28 March 2010 07:04, Giles Lean <giles...@pobox.com> wrote:
>
>>
>> o there has been some positive discussion of making error
>> handling easier (I do agree if err != nil { ...} isn't
>> scintillating to spend time writing: if those ideas lead to
>> something it would be good)
>>
>
>I wondered about a construct of the form
>
> try y := f(x)
>
>f is expected to return two results. The first is assigned to y. The second
>is an error value. If it is non-nil, then the executing function returns.
>The error value is assigned to its final result variable; any other results
>have their zero values. Thus this construct is shorthand for
>
> y, err := f(x); if err != nil { return zero1, zero2, ..., err }


It seems like the idea there is to evade one of the main intents behind
discouraging overuse of panic(), which is to encourage the programmer to
actually look at errors and do something sensible about them. Returning
all zeros like that, and doing it in function after function, up the call
chain -- well, the code might not admit it was panicking, but it'd be
obvious to onlookers that it was scared s__tless, and running for the
hills at full speed.

That's especially so since, like a defeated soldier throwing away
equipment so that he can run faster, the code would be jettisoning local
knowledge of what caused the error. An error like "No such file or
directory" only has meaning if you know what file operation was being
tried; if you just slam all those sorts of error messages up to a higher
level without any modification, that higher level will have no idea which
attempt at a file operation led to that message, and thus will not be
able to recover from the error.

If that sort of thing is really what you want to do, then just argue for
calling panic, perhaps via the syntax:

y, panic = f(x)

where assignment to 'panic' of anything non-nil (or for booleans, as
you've suggested, assignment of the value false) would translate to a
call of panic() with that argument. You're not going to be able to
sanely recover from the error, and probably won't even be able to display
a good error message; but sometimes you just don't care about that, and
calling panic is still better than the already-available alternative

y, _ = f(x)

which just eats the error and marches blindly on, and which would
otherwise be the choice of lazy or overhurried programmers.

Rob 'Commander' Pike

unread,
Mar 28, 2010, 11:03:24 PM3/28/10
to Giles Lean, golang-nuts Nuts

On Mar 28, 2010, at 5:54 PM, Giles Lean wrote:

>
> Rob 'Commander' Pike <r...@google.com> wrote:
>
>> During panicking, if a deferred function invocation calls
>> recover, recover returns the value passed to panic and stops
>> the panicking.
>
> Got that.
>
>> At any other time, or inside functions called by the
>> deferred call, recover returns nil.
>
> Will this be enforced by the runtime (I think so) or the
> compiler?

The runtime.

> If it's enforced by the runtime, then I can have a function
> that in some call paths is directly deferred, and in others
> called from a deferred function and any recover() calls it
> makes will be ignored.

Yes.

>
> Mind, I'm not sure I'd want to do this; I just suspect it
> might be useful sometime (if a bit undisciplined, but urgent
> maintenance sometimes has to cut corners) and am curious to
> know if it will be possible or not.
>
> As you can see I'm reduced to nitpicking questions. After
> seeing a weekend's worth of postings go by (too many of them
> mine) and reviewing your proposal and the ensuing thead again
> this morning this is all I can come up with. I usually manage
> to ding the target, but the target's not usually as
> challenging as a Rob 'Commander' Pike proposal! (You realise
> the frustration you've caused, right?? :-)
>
> I think (after coffee) I'll go soothe my nerves opening some
> new CL's. Which you can blame on Russ or someone, yes. <grin/>
>
> Thanks,
>
> Giles
>
> P.S. At least with panic("these are not exceptions") I still
> get to maintain my dislike of exceptions. "Oh, Go? No, it
> doesn't have exceptions. You can call panic() and recover
> from it sometimes, but functions aren't expected to throw
> exceptions, none of that stuff." :-)
> --
>

> 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 28, 2010, 11:04:27 PM3/28/10
to jimmy frasche, Giles Lean, golang-nuts Nuts

Safer yes but overly fussy I think. Don't give panic a nil value. The changes of that happening are infinitesimal in real code.

-rob

jimmy frasche

unread,
Mar 28, 2010, 11:15:30 PM3/28/10
to Rob 'Commander' Pike, Giles Lean, golang-nuts Nuts
On Sun, Mar 28, 2010 at 8:04 PM, Rob 'Commander' Pike <r...@google.com> wrote:
> Safer yes but overly fussy I think. Don't give panic a nil value. The changes of that happening are infinitesimal in real code.

I suppose as long as the stack trace indicates that someone panic'd on
a nil it would be annoying but not impossible to deal with, but if
we're talking about, say, a server that's dynamically loaded a module
(should this ever be doable) the results would be unpleasant even if
easily diagnosed.

roger peppe

unread,
Mar 29, 2010, 7:01:20 AM3/29/10
to r...@golang.org, Esko Luontola, golang-nuts
On 28 March 2010 20:48, Russ Cox <r...@golang.org> wrote:
> defer func() {
[...]
> }

i make that mistake all the time too.

> (3) The code below parses a specific structure from
> a byte array, relying on the array bounds checks to
> catch malformed input.

it's in just this kind of example that i'm concerned about the
performance impact of this proposal. i tried a little benchmarking
test which indicated that the given function runs at about twice
the speed without the defer/recover clause. (code attached)

with the recovery block statically available to the compiler, i
think it should be possible to have near zero overhead in the
common (non-panic) case.

one possibility: recover could be a keyword with very
similar semantics to defer - except that the code would only be called
when panicing. the argument to recover could be a function with a
single argument.
potentially the function would only be called when the type of the panic
value matches the argument type.

func UnmarshalDir(b []byte) (d *Dir, err os.Error) {

recover func(_ interface{}) {


err = ProtocolError("malformed Dir")
}

[....]
}

on the other hand, maybe defer just needs to be made more efficient.

panictest.go

Eleanor McHugh

unread,
Mar 29, 2010, 7:24:55 AM3/29/10
to golang-nuts
There's a hatred of exceptions on this list that I assume must be the result of some deeply scarring shared Java or C++ experience. Not having touched either language in any serious way for five or six years now I don't share that pain and can only go on my experience of exceptions in Ruby, which has been an overwhelmingly positive one. Therefore I'm going to take the bull by the horns and provide a skeleton approximation of how Ruby-style exceptions might be handled in Go using the panic/recover mechanism.

import ( "os"; "container/vector"; "runtime"; "strings" )

var BacktraceDepth int

type Exception struct {
call_stack *[]uintptr
Error os.Error
}

func (e Exception) Raise() {
if BacktraceDepth > 0 {
stack := make([]uintptr, BacktraceDepth)
n := runtime.Callers(2, stack[0:])
e.call_stack = stack[0:n]
}
panic(e)
}

func (e Exception) Backtrace() (v vector.StringVector) {
for _, pc := range e.call_stack {
name := runtime.FuncForPC(pc).Name()
if i := strings.Index(name, "."); i >= 0 {
name = name[i+1:]
}
v.Push(name)
if name == "main" {
break
}
}
return
}

func (e Exception) Identical(o *Exception) bool {
return e.Error == o.Error
}

func (e Exception) Rescue(o *Exception, f func()) bool {
if e.Identical(o) {
if f != nil {
f()
}
return true
}
return false
}

func Recover() Exception {
x := recover()
if x == nil {
return nil
}
if e, ok := x.(Exception); !ok {
panic(x)
}
return e
}

lack of syntax support makes usage clunkier than I'd like, but here's an example based on Rob's of how they might be handled:

import . "exceptions"

var Success Exception = Exception{}
var Panic Exception = Exception{ Err: 1 }

func recovery() {
if x := Recover(); x != nil {
switch {
case x.Rescue(Panic, func() {
fmt.Printf("don't panic!!!")
panic(x)
}):
case x.Rescue(Success, nil):
}
} else {
fmt.Printf("really nothing to worry about")
}
}

func f(dopanic bool) {
defer recovery()
fmt.Printf("before")
p(dopanic)
fmt.Printf("after")
}

func p(dopanic bool) {
if dopanic {
Panic.Raise()
}
}

I probably wouldn't use this mechanism in practice because the switch statement is somewhat counter-intuitive but that's not really the point of the example. With panic/recover there is already sufficient infrastructure to implement at least a subset of the exceptions I'm used to in Ruby and I'd wager that production code will demonstrate equally buggy, half-implemented versions where the payoff is sufficient (i.e. the ability to extract a stack trace for programmatic analysis).

That being the case, if panic/recover are entering the language then a proper exception-handling mechanism should as well.

roger peppe

unread,
Mar 29, 2010, 7:49:55 AM3/29/10
to Eleanor McHugh, golang-nuts
On 29 March 2010 11:24, Eleanor McHugh <ele...@games-with-brains.com> wrote:
>        func Recover() Exception {
>                x := recover()

not to comment on the rest of your code, but this won't work.

"the recover function can return non-nil only when it is executed
directly by a deferred function while panicking."

i imagine that the compiler might give an error message in
the above case.

Eleanor McHugh

unread,
Mar 29, 2010, 8:03:18 AM3/29/10
to golang-nuts

Quite possibly, in which case the function would be another bit of inline boilerplate :)

chris dollin

unread,
Mar 29, 2010, 8:28:07 AM3/29/10
to Norman Yarvin, Giles Lean, Daniel T, golang-nuts
On 29 March 2010 03:42, Norman Yarvin <yar...@yarchive.net> wrote:
On Sun, Mar 28, 2010 at 08:01:35AM +0100, chris dollin wrote:
>On 28 March 2010 07:04, Giles Lean <giles...@pobox.com> wrote:
>
>>
>> o there has been some positive discussion of making error
>>  handling easier (I do agree if err != nil { ...} isn't
>>  scintillating to spend time writing: if those ideas lead to
>>  something it would be good)
>>
>
>I wondered about a construct of the form
>
>  try y := f(x)
>
>f is expected to return two results. The first is assigned to y. The second
>is an error value. If it is non-nil, then the executing function returns.
>The error value is assigned to its final result variable; any other results
>have their zero values. Thus this construct is shorthand for
>
>  y, err := f(x); if err != nil { return zero1, zero2, ..., err }

It seems like the idea there is to evade one of the main intents behind
discouraging overuse of panic(), which is to encourage the programmer to
actually look at errors and do something sensible about them.

No, the idea is to make such buck-passing explicit and straightforward.
When it's the right thing to do, it shouldn't be clumsy.

It's my belief that the programmers who will just throw all their errors
up to the caller won't be deterred by having to write the longhand form.

 Returning
all zeros like that, and doing it in function after function, up the call
chain -- well, the code might not admit it was panicking, but it'd be
obvious to onlookers that it was scared s__tless, and running for the
hills at full speed.

"In function after function" is a bit speculative, isn't it? It's certainly
possible. Will it happen in code someone cares about?
 
That's especially so since, like a defeated soldier throwing away
equipment so that he can run faster, the code would be jettisoning local
knowledge of what caused the error.  An error like "No such file or
directory" only has meaning if you know what file operation was being
tried; if you just slam all those sorts of error messages up to a higher
level without any modification, that higher level will have no idea which
attempt at a file operation led to that message, and thus will not be
able to recover from the error.

This you can do already. 
 
If that sort of thing is really what you want to do,

I don't. It was precisely the /local/ behaviour I wanted. Perhaps I'm
optimistic.

--
Chris "allusive" Dollin

roger peppe

unread,
Mar 29, 2010, 8:46:22 AM3/29/10
to Eleanor McHugh, golang-nuts
On 29 March 2010 11:24, Eleanor McHugh <ele...@games-with-brains.com> wrote:
>        func recovery() {
>                if x := Recover(); x != nil {
>                        switch {
>                                case x.Rescue(Panic, func() {
>                                        fmt.Printf("don't panic!!!")
>                                        panic(x)
>                                }):
>                                case x.Rescue(Success, nil):
>                        }
>                } else {
>                        fmt.Printf("really nothing to worry about")
>                }
>        }

instead of this, you could do something like this:

func recovery() {
switch x := recover().(type) {
case nil:


fmt.Printf("really nothing to worry about")

case Panic:


fmt.Printf("don't panic!!!")
panic(x)

default:
panic(x)
}
}

i don't really see what advantage you gain from having an Exception type.

It is loading more messages.
0 new messages