5 ms·
> ...as it makes the contract between a caller and a callee hard to determine, and makes your code highly coupled. I prefer the Go or Rust style of handling it
by otabdeveloper4 2y ago
> ...as it makes the contract between a caller and a callee hard to determine, and makes your code highly coupled. I prefer the Go or Rust style of handling it through return values.
There is literally (literally!) no difference at all between throwing and exception and returning it as a variable. Except for the fact that in the exception passing style you have to write the boilerplate by hand, instead of letting the compiler do it for you.
Why anybody with a sane and functioning brain would want to do that by hand in 2024 I will never understand.
- ratscylla 2y agoI think people like it because the control flow of a given program is more obvious when you write it that way. No one can "throw Foo" three libraries down from their caller as an "API". See https://go.dev/blog/errors-are-values https://go.dev/blog/errors-are-values
- wvenable 2y agoYou're just trading one type of control flow visibility for another. With even the most basic amount of error-return handling the actual control flow of your program is quite obscured.
- psd1 2y agoAs an implementation detail, exceptions are usually much more expensive than just popping the stack, as computation is needed for each frame you traverse. Having both throw and return is like regex: now you have two problems.
- tylerhou 2y agoExceptions are less expensive in the unexceptional case. Consuming a Result value requires a branch to see whether it holds Error. That is not necessary if the function returns a value directly (and throws an exception on error).
- vacuity 2y agoHowever, exceptions require a larger runtime and can make optimizations harder to reason about.
- zerodensity 2y agoI have heard this before but never really understood it. Take this: ``` int bar(int arg); // May throw int foo(int arg) { int b = bar(arg); return b * 5; } ``` VS: ``` Error<int> bar(int arg); // Uses error type Error<int> foo(int arg) { auto b = bar(arg); if(b.ok()) { // Happy Path return b.unwrap() * 5; } return e; // Exceptional Path } ``` In the happy case (no exception) how can the first version be harder to optimize than the second one? In the exceptional case exceptions will probably be slower. You trade fast common case for slow exceptional case so it makes sense to me. Is the slower one the one that is hard to optimize? Is this what we are talking about when optimizations are harder to reason about? Or are there some other things that become harder because of exception? Things like RAII, defer, goto stuff?
- vacuity 2y agoAdmittedly, I'm not an expert. I believe it's due to the extra control flow points (assume all functions can throw, not just specially marked ones) and not having just a local value, which procludes analyzing the operations done on the error path.
- Kamq 2y ago> In the happy case (no exception) how can the first version be harder to optimize than the second one? If your runtime has to do extra work to support stack-unwinding, it could easily be slower. You're doing work in the happy path in that case just in case the sad path happens. My guess is adding function metadata to some sort of datastructure (linked list?) so that it can figure out whats happening when it long jumps to the handler. I'd bet that allocation has at least one conditional in it. The alternative would be putting the metadata on the stack, letting the function complete normally, and then check a flag to see which path you're on (same overhead). To be clear, I have no idea how various runtimes implement this, just that the extra magic could easily have the same, or more, overhead than a conditional check.
- humzashahid98 2y agoI prefer to build result-passing, no-exception-throwing systems out of an exception-throwing core where the language itself may throw exceptions but the thing I build from the language always returns result. Elm is an extreme case where indexing into an array returns a result type instead of an exception when the index is out of bounds, unlike most languages including Haskell. Maybe my program logic is intended to always access only valid indices in an array, but here I'm given a result type where I have nothing to do in the error case since my code is never intended to reach that case. I would rather let the language throw an out-of-bounds exception here to tell me that my implementation is incorrect while I'm testing. Same with libraries in a language. It really depends on the use cases of the library you're writing whether results or exceptions are better. The most convenient thing for the user of a library would be to provide both exception-throwing and result-passing alternatives. This is what the OCaml standard library does as well.
- shepherdjerred 2y agoI dislike Go, but I understand why people like most of its decisions. I cannot fathom why people think it does error handling well, though. The codebase I work in has _so_ many errors that are completely ignored, and errors are a lot harder to track down. These problems can be solved by writing better code, but the problem is that it's, of course, hard to write good code. Java's exception handling has problems, but at least it gives you nice stack traces and you can't forget to propagate an exception.
- Kamq 2y ago> I cannot fathom why people think it does error handling well, though. It doesn't. But it lets me do error handling well if I'm up to it. Which is the worst of all possible worlds except for the one where error handling is done badly. > Java's exception handling has problems, but at least it gives you nice stack traces and you can't forget to propagate an exception. Y'know, I actually had the chance to use raw java (and even a modern version of java to boot) for something semi-recentish, and it was pretty great. I was surprised how well it worked out. Unfortunately, this wasn't the experience I got on any sort of enterprise java project though. The stack traces usually truncated before it got out of the framework being used. I'm not really working in go because I'm rejecting java itself. I have certain disagreements with the design philosophy, especially in its early days, but it's reasonably possible to write decent code in it. I refuse to work in java because of its ecosystem. I know what writing java is like in an enterprise setting, and I'll take 1000 `if err != nil`s over that experience.
- shepherdjerred 2y agoEnterprise Java should really be called something else because it ruins the name of vanilla Java. Vanilla Java is excellent. I miss some language features, but recent updates have really made the language competitive. On the other hand, enterprise Java continues to be what everyone thinks of when "Java" is mentioned. It's also terrible.
- kdmccormick 2y agoUnless you're using exclusively Java-style checked exceptions, then there literally (literally!) is huge difference. That is: def main(): try: my_a = a() except ExA, ExB: my_a = None ... def a() -> A: ... my_b = b() ... def b() -> B: ... If b changes its signature to return type C, then it is a type error in a. main doesn't need to worry about what b returns; only a does. BUT if b begins raising ExC instead of ExB, then that will break main at runtime. That is, main needs to be aware of what b could raise, even though it doesn't directly call it.
- wvenable 2y agoThis is unrealistic example and has error handling completely backwards. The exception handler in main() only knows how to handle specific types of errors -- it doesn't know or care where they are thrown, that's not it's problem. Lets say, for example, it knows it can handle network errors by retrying the operation. If b() changes to throw a different type of exception that main() doesn't know to handle them main will break. And breaking is entirely the correct behaviour. Maybe b() is an interface and the actual implementation might not even have existed when main() was written. Maybe yesterday it was implemented with a file, tomorrow with a HTTP call, and maybe next week something else. But tomorrow, at least, it can retry the operation when it fails.
- kdmccormick 2y agoThat's all great in theory, but in practice, I see except clauses mostly used to handle particular exception classes that callees are known to throw.
- wvenable 2y agoMy applications have very few exception handlers and most don't do any "handling" at all except logging. My most robust application is a desktop application with a single exception handler at the event loop that merely prints the exception message in an a message box. If you try and save a file to a bad network location or something, just click ok and try again.