3 ms·
> where library code can suddenly abort and the author shrugs and says "shoulda read the docs". In see no difference. Except for the fact that you don't have t
by groestl 4y ago
> where library code can suddenly abort and the author shrugs and says "shoulda read the docs".
In see no difference. Except for the fact that you don't have the stacktrace (in Go), and unroll the Stack manually. In any case, there'll be a generic catch (whatever you call it) on the top level of your daemon to recover from the failed request and handle the next.
- gthrowaway52 4y agoIn Go the programmer is forced to make a decision on what to do with the error. Common patterns include (A) return an annotated error, (B) log it and continue, (C) retry, (D) aggregate the errors in some way. If you believe the only way to handle an error is (A), then Go's design makes no sense.
- groestl 4y agoYou seem to believe options B to D are not available to programmers in languages with exceptions. The real value comes from making option E much more unlikely: ignoring both the result and error value altogether, because you relied on the side effect of the function you called.
- nindalf 4y agoYou might like how Rust does error handling. Rather than a function returning a triple of (val, error) where the error can be ignored, functions return Result<T, E>. If you want to get the T, you must write code that handles both possibilities. If your function instead wants the T and propagate the E upwards if it exists, you can do that with one character - “?”
- groestl 4y agoI indeed like Rust's approach a lot more than Go's. What I like less still is that it gives the impression that it's even possible to define functions that cannot fail. This is not true. One just has to look at how runtimes deal with stack overflow errors to see how the good old Java RuntimeException creeps in in various forms (e.g. panics) because checked exceptions and it's recent incarnation as error values are a leaky abstraction.
- nindalf 4y agoRust makes a distinction between recoverable and unrecoverable errors. Recoverable errors are the E in Result<T, E>. You can take action and recover, depending on what kind of E it is. Unrecoverable errors are things like stack overflows or out of bounds array access. There is no reasonable way to soldier on after this, so the program should just end. Trying to continue the program in such situations only leads to pain. Like array accesses out of bounds that allow you to read unrelated memory. But it’s still an evolving area. For example, failure to allocate memory - is that recoverable or unrecoverable? Initially it was thought that it was unrecoverable, and programs would panic if memory failed to allocate. This seemed reasonable, until folks tried to use Rust within the Linux kernel. Within the kernel, failure to allocate memory is recoverable. Rust is evolving the semantics here. All this to say, yes, Rust does allow you to define functions that either fail in a recoverable way, in which case the calling function should handle it. Or they fail in an unrecoverable way in which case there’s nothing the calling function can do to recover. Thankfully, panics in third party code are relatively rare so this doesn’t happen in practice.
- groestl 4y ago> Unrecoverable errors are things like stack overflows or out of bounds array access. There is no reasonable way to soldier on after this, so the program should just end No, I wholeheartedly disagree with this. It's the equivalent of exit(1) some way down the stack. Whats recoverable or not depends on the use case and is a decision to be made by the caller of a function, not the implementor.
- Farmadupe 4y agoGP might have been referring to undefined/invalid behaviour (whether in the language or in some OS syscall or whatever). After the demons came out of your nose you can never fix the problem, so there is no point trying to handle the error. Otherwise I agree with you, that library code should not fail/crash/exit(1) just because of some judgement about recoverability, and out to clean up after itself before passing control back to the caller. If the user wants to fix some ENOSPC deep in my library by shelling out to "rm -rf /" and then trying again, that's fine by me, and this should be reflected in the API.
- gthrowaway52 4y agoI'm talking about unchecked exceptions. It's not about what is possible it's about what patterns a language encourages. It feels like we've lost the thread of discussion. - You say there is no difference between unchecked exceptions and Go's errors - I say yes there is since Go forces users to handle errors explicitly - You say that's not technically true in all cases. OK. Yes. I should have said "nudges users" instead of force. It's a shortcoming of the language. It is still really hard for me to see unchecked exceptions and value-based error handling as the same thing. One of them encourages doing nothing and hoping that bubbling up is the right answer. Very often, especially in a multi-threaded context, it is not.
- groestl 4y ago> You say there is no difference between unchecked exceptions and Go's errors Where do I say that? I say that Go programmers, in 99.9% of cases, do manually what exceptions do automatically. In terms of cumbersome, error values are the equivalent of checked exceptions. The equivalent of runtime exceptions are panics.
- gthrowaway52 4y agoMaybe I misinterpreted you. I don't disagree that checked exceptions and errors are ~the same thing. > Go programmers, in 99.9% of cases, do manually what exceptions do automatically Our experience working with Go must be very different. Grepping for "err := " and looking at the first 10 results in my team's codebase. * 4 cases where the error is just returned * 1 case where the error is returned only if it matches a certain type (otherwise logged) * 1 case where the error is logged as a warning. * 1 case where the error is logged, some metric is incremented, and then execution continues as usual. (a fail-open authentication check) * 1 case where the error is returned as different error type. * 1 case where the error is returned, but only after accessing the result (this is a strange design / antipattern), and annotating it with a human-readable explanation. * 1 case where the error is treated as a boolean condition, and is not returned. (the error condition is "does not exist"). So in this sample it matches the "automatic behavior" in only about half the cases. In other cases, substituting the existing behavior with exception's automatic behavior would cause severe bugs.
- flanked-evergl 4y ago> In Go the programmer is forced to make a decision on what to do with the error. Actually go runs perfectly fine if you just do nothing with the error, and then you are flying past errors like it is nobody's business. Of course you can you static analysis to tell you if you forgot to handle an error, and most people do, but that is not really a language feature. In Java a programmer is also forced to make a decision to add explicit exception handling or not. But if they choose to not add it, the program won't just pretend like all is fine and dandy, it will stop the execution of the current function and pass the exception up the stack.
- lolinder 4y agoThe difference is that the error states are explicit rather than implicit. The function's signature tells you what the possible errors you'll need to handle are, so you don't get caught off guard at runtime. As for stack traces: checked exceptions are semantically almost identical to error-as-value but they do give you a stack trace. They give you all the static guarantees of error-as-value plus the benefit of being able to know where the error came from.
- groestl 4y ago> checked exceptions are semantically almost identical to error-as-value but they do give you a stack trace I completely agree with you on that one. The only thing I'd want to add is that aside from example code, usually you don't care a lot about what specific errors might pop up, unless you're at the top level of your program. This might be the request handler, or some UI loop. On the other hand, where you care about errors, you usually don't care about results or their types. I've been in the industry long enough that I vividly remember the catch-and-wrap orgies in Javaland of 2003. A lot of it was caused by the inferior type system of Java, which hinders composability (such as when a layer is moved to another node and suddendly you have to deal with remoting exceptions). But it also goes to show that knowing a specific class of error is often overrated in application code, and should be, well, the exception.