The similarity in both needing to have stack trace does not make them very alike yet. They serve similar purpose and for that purpose the stack trace serves value to the developer.
Returns go straight up the stack only if you choose them to. Caller doesn't have to propagate errors and return immediately, it can hold onto them and/or process them in the natural place they occur, they are just values. The returns from your function can be found with "grep return".
Exceptions, by default, break the natural control flow unless you wrap everything with try/catch. Even then, a lot of constructs won't be very natural and you really will have to get out of your way to identify the source of the exception, which very frequently is more important than it's type.
Why would finding the source of the exception be difficult? The stack trace in exception-based languages goes back to the actual line, as opposed to a stack trace from an error-as-value based language, where it only goes to where you trigger generating the stack trace.
I meant finding it in the context of a coder who writes a function. It's hard to identify which expression and statements can cause exceptions and effectively short-circuit your function. In contrast to explicit errors-returned-as-values.
(Panics of course can cause similar thing in Go but that's the reason why they should rarely be recovered and not used as a value propagation mechanism.)
Is that any worse than the ambiguity in e.g. code like this?
func thing(arg) {
otherThing(arg) // does this return an error you're ignoring?
}
It even has a similar problem, where void returns -> err returns on code changes are not visibly discoverable (similar to a newly-throwing func).
Granted, you could turn this into a compiler error. But it's not currently.
---
Anyway. Given that so many not-prevented-by-the-typesystem operations panic, I don't think it's reasonable to assume that any func call will not panic, especially not in the future since they may change. So you already have to program with `defer` to maintain your invariants, which is exactly the same as with exceptions, except you now have to deal with both possibilities for nearly every func.
Returns go straight up the stack only if you choose them to. Caller doesn't have to propagate errors and return immediately, it can hold onto them and/or process them in the natural place they occur, they are just values. The returns from your function can be found with "grep return".
Exceptions, by default, break the natural control flow unless you wrap everything with try/catch. Even then, a lot of constructs won't be very natural and you really will have to get out of your way to identify the source of the exception, which very frequently is more important than it's type.