5 ms·
Go exceptions do carry stack trace information. However, the topic is errors, not exceptions. Those are very different concepts. Of what use is stack trace inf
by randomdata 5y ago
Go exceptions do carry stack trace information.
However, the topic is errors, not exceptions. Those are very different concepts. Of what use is stack trace information in debugging values that you have assigned error meaning to when not other types of values?
If you had a function
func add(a, b int) int { return a * b }
there would be no expectation of carrying a stack trace to debug it. So what's different about
func add(a, b int) error { return errors.New("cannot add") }
that does require a stack trace?
- dragonwriter 5y ago> However, the topic is errors, not exceptions. Idiomatically, go uses errors for the purposes other languages use exceptions, so if this makes debugging harder, it's an important consideration.
- randomdata 5y agoThat is also true of most languages. Java (and Javascript in its attempt to copy it) are about the only languages that actually promote using exceptions for errors, and in hindsight I think we can agree it was a poor design decision. That doesn't stop people from trying to overload exceptions in other languages, Go included, but in terms of what is idiomatic... However, the question was asking what is different about errors compared to other values. The add function above contains an error, yet I don't know of any language in existence where you would expect a stack trace bundled alongside the result to help you debug it. Why would that need change just because you decided to return a struct instead of an int? And actually, many APIs in the wild do represent errors as integers.
- dragonwriter 5y ago> Java (and Javascript in its attempt to copy it) are about the only languages that actually promote using exceptions for errors Python does. Ruby does. It's not just Java and JS. Go is very open about its approach being a departure. > And actually, many APIs in the wild do represent errors as integers. Many, many APIs in the wild are implemented in (or meant to be consumed from) C, which doesn't even have exceptions, so not using exceptions makes sense for them. Very often idiomatic non-C host language wrappers for those APIs will fire exceptions when they get an error return.
- randomdata 5y ago> Ruby does. Definitely not. Especially because early Ruby implementations brought huge overhead when exceptions were used, you were strongly advised to only use exceptions for actual exceptions. Ruby was one of the first languages that really started pushing the idea that exceptions should be reserved for exceptions, even if was just for technical reasons. Those overhead problems have been addressed and are no longer a problem, but the sentiment has continued to ring true. I agree that doesn't stop people from trying to overload them, as I said earlier. But idiomatic? Not at all.
- rezonant 5y agoRuby has two kinds of exception handling. There is try/catch, this is used for flow control and should not be used for error handling (it doesn't have stack traces). And then there is raise/rescue. Using try/catch as intended is a bit of an art, but raise/rescue is everywhere. It is absolutely the primary way of handling errors. I think you might be confusing these two.
- kortex 5y ago> Python does And it's awful. I use EAFP locally (to avoid TOCTOU and the like) at low level interfaces but I don't let it bubble up out of a function scope, because it is a goto in all but name. I've also been increasingly using the `result` library/data structure. It's incredibly liberating to return an error as an object you can compose into other functions, vs try/catch, which does not compose. Yes I write python almost like rust, and it's great. Strong types, interfaces, immutable types. It looks nothing like "old school python" but also behaves nothing like it. Gone are the day of "oh it crashed again, fix one line and rerun". Exceptions should be for exceptional circumstances, not errors. Edit: I see this is controversial. What do you take objection to? Making your python look less dynamic and more like rust? Try it before you knock it. Python's my favorite language, but I do not agree that many of the common "pythonic" patterns are good at scale. https://returns.readthedocs.io/en/latest/pages/result.html https://returns.readthedocs.io/en/latest/pages/result.html
- eru 5y ago
- linkdd 5y agoCurrent state of the art: func read_file(filename string) (string, error) { return "", errors.New("oops") } func foo() error { a, err := read_file("a.txt") if err != nil { return errors.New(fmt.Sprintf("read a: %s", err)) } b, err := read_file("b.txt") if err != nil { return errors.New(fmt.Sprintf("read b: %s", err)) } // do stuff with a and b return nil } func main() { err := foo() if err != nil { fmt.Fprintln(os.Stderr, err) } } In a language with exceptions: func read_file(filename string) string { throw FileNotFound(filename) } func foo() { a := read_file("a.txt") b := read_file("b.txt") // do stuff with a and b } func main() { try { foo() } catch (err FileNotFound) { fmt.Fprintf(os.Stderr, "file not found: %s\n%s\n", err.filename, err.Stacktrace()) } } With the current go error handling, you need to add the informations yourself in the string, not as a real data structure. And before you say "you can add the filename to the error message in read_file()", what if the function is defined in a dependency you have no control over? An exception is a typed data structure that contains way more informations and value to automate rescuing. Delegating error handling to a try/catch block with a typed data structure allows the caller to care for certain type of errors and delegate the others to its own caller. With the current error type in Go, what would you do? parse the error message?
- randomdata 5y agoYour code is wrong. It would normally be written something like this: func read_file(filename string) string { panic(FileNotFound{filename}) } func foo() { a := read_file("a.txt") b := read_file("b.txt") // do stuff with a and b } func main() { defer func() { if err, ok := recover().(FileNotFound); ok { fmt.Fprintf(os.Stderr, "file not found: %s\n%s\n", err.filename, err.Stacktrace()) } } foo() } However, exceptions are meant for exceptional circumstances (hence the name), not errors. A file error is not exceptional in the slightest. It is very much expected. While you can overload exceptions to pass errors (or any other value), that does not mean you should. Your use of exceptions for flow control (i.e. goto) is considered harmful.
- linkdd 5y ago