Readit News logoReadit News
SuperV1234 · a month ago
This post completely misunderstands how to use exceptions and provides "solutions" that are error-prone to a problem that doesn't exist.

And this is coming from someone that dislikes exceptions.

marler8997 · a month ago
I avoid using exceptions myself so I wouldn't be surprised if I misunderstand them :) I love to learn and welcome new knowledge and/or correction of misunderstandings if you have them.

I'll add that inspiration for the article came about because It was striking to me how Bjarne's example which was suppose to show a better way to manage resources introduced so many issues. The blog post goes over those issues and talks about possible solutions, all of which aren't great. I think however these problems with exceptions don't manifest into bigger issues because programmers just kinda learn to avoid exceptions. So, the post was trying to go into why we avoid them.

xerokimo · a month ago
RAISI is always wrong because the whole advantage of the try block is to write a unit of code as if it can't fail so that what said unit is intended to do if no errors occur is very local.

If you really want to handle an error coming from a single operation, you can create a new function, or immediately invoke a lambda. This would remove the need break RAII and making your class more brittle to use.

You can be exhaustive with try/catch if you're willing to lose some information, whether that's catching a base exception, or use the catch all block.

If you know what all the base classes your program throws, you can centralize your catch all and recover some information using a Lippincott function.

I've done my own exploring in the past with the thought experiment of, what would a codebase which only uses exceptions for error handling look like, and can you reason with it? And I concluded you can, there's just a different mentality of how you look at your code.

thegrim33 · a month ago
No offense, but why did you decide to write an instructional article about a topic that you "wouldn't be surprised that you misunderstand"? Why are you trying to teach to others what you admittedly don't have a very solid handle on?
mixedmath · a month ago
I'm not very familiar with proper exception usage in C++. Would you mind expanding a bit on this comment and describing the misunderstanding?
guy2345 · a month ago
what about the misreported errno problem?
binary132 · a month ago
obviously the errno should have been obtained at the time of failure and included in the exception, maybe using a simple subclass of std exception. trying to compute information about the failure at handling time is just stupid.
smallstepforman · a month ago
My biggest beef with exceptions is invisible code flow. Add the attribute throws<t> to function signature (so that its visible) and enforce handling by generating compiler error if ignored. Bubbling up errors is OK. In essence, this is result<t,e>. Thats OK. Even for constructors.

What I dislike is having a mechanism to skip 10 layers of bubbling deep inside call stack by a “mega” throw of type <n> which none of the layers know about. Other than

gpderetta · a month ago
I would also like to have type checked exceptions in c++ (as long as we can also template over exception specifications).

But what I would really want is noexcept regions:

   <potentially throwing code>...
   noexcept {
      <only noexcept code here> ...
   }
   <potentially more throwing code>...
i.e. a way to mark regions of code that cannot deal with unwind and must only call non-throwing operations.

wakawaka28 · a month ago
Don't think of the uncaught type as a "mega" throw. It's just a distinct type of error that nobody specified that they can handle. If you truly worry about the caller missing something, then somewhere in there you can catch anything and translate into a recognizable exception. This is easiest to understand in a library. The interface functions can catch all and translate into one particular exception type for "unknown" or generic errors. Then, that will be caught by anyone using the thing as documented. This only works if it's just reporting a non-fatal error. In case of a fatal error, it can't be handled, so the translation is kind of pointless.
Flundstrom2 · a month ago
Java and Rust are the only language that I know of that has proper handling of exceptions; mandatory declaration as part of method declaration, since exceptions ARE an integral part of the contract. (Yes, I consider the Result<T, E> being corresponding to exception declaration, since the return value MUST be checked prior to use of T.)
nielsbot · a month ago
Swift user here: I have to say one of the best features of Swift is the exception handling. Which is to say, exceptions in Swift are not C++/Java/Obj-C style exceptions, but instead are a way to return an error result from a function. And Swift enforces that the error is handled.

That is, a `throw` statement in Swift simply returns an `Error` value to the caller via a special return path instead of the normal result.

More explicitly, a Swift function declared as:

    func f() throws -> T {
    }
Could be read as

    func f() -> (T|any Error) {
    }

More here: https://github.com/swiftlang/swift/blob/main/docs/ErrorHandl...

thomasmg · a month ago
I saw that in Swift, a method can declare it throws an exception, but it doesn't (can't) declare the exception _type_. I'm not a regular user of Swift (I usually use Java - I'm not sure what other languages you are familiar with), but just thinking about it: isn't it strange that you don't know the exception type? Isn't this kind of like an untyped language, where you have to read the documentation on what a method can return? Isn't this a source of errors itself, in practise?
mayoff · a month ago
Another really nice thing about Swift is that you have to put the `try` keyword in front of any expression that can throw. This means there's no hidden control flow: if some function call can throw, you're informed at the call site and don't have to look at the function declaration.
fakwandi_priv · a month ago
From what I can read Swift gives you a stack trace which is good. At the moment I’m using Go where that stack is only generated where the panic is triggered, which could be much higher up. Makes it a lot more unwieldy to figure out where an error happens because everyone uses:

> if err != nil return err

binary132 · a month ago
that sounds very similar to noexcept(bool) to me, except that noexcept can be computed, for example by deriving from some generic trait, and we presume throws unless specified non-throwing by noexcept.
morshu9001 · a month ago
In high-level code, pretty much everything can fail in many different ways, and usually you're either just passing the error up or handling it in some catchall manner. Rust's behavior makes sense for its use cases, but it'd get exhausting doing this in like a web backend.
wahern · a month ago
It's already too exhausting to use for OOM, which is why Rust effectively punted on that from the very beginning. And the ironic thing is that anyhow::Error (or similar) seems poised to become idiomatic in Rust, which AFAIU always allocates, while the extremely belated try_ APIs... does anybody even use them?

It's a shame. Were I designing a "low-level" or "systems" language, rather than put the cart before the horse and pick error results or exceptions, my litmus test would be how to make handling OOM as easy as possible. Any language construct that can make gracefully handling OOM convenient is almost by definition the fabled One True Way. And if you don't have a solution for OOM from the very beginning (whether designing a language or a project), it's just never gonna happen in a satisfactory way.

Lua does it--exceptions at the language level, and either setjmp/longjmp or C++ exceptions in the VM implementation. But for a strongly typed, statically compiled language, I'm not sure which strategy (or hybrid strategy) would be best.

munchler · a month ago
`Result<T, E>` comes from Haskell's `Either a b` type. F# also has a `Result<'T, 'E>` type.

It's funny how often functional programming languages lead the way, but imperative languages end up with the credit.

raincole · a month ago
Actually (insert nerd emoji) this is a direct descendant from tagged union type, which existed in ALGLO 68, an imperative language.

Java's checked exception is just an (very anti-ergonomic) implementation of tagged union type.

paulddraper · a month ago
Algebraic types, immutable structures, lambdas, higher-order functions.

I think FP receives a lot of "credit."

Albeit the "pure" FP languages aren't popular, because 99% FP is really hard.

barrkel · a month ago
Don't forget, failure modes pierce abstraction boundaries. An abstraction that fully specifies failure modes leaks its implementation.

This is why I think checked exceptions are a dreadful idea; that, and the misguided idea that you should catch exceptions.

Only code close to the exception, where it can see through the abstraction, and code far away from the exception, like a dispatch loop or request handler, where the failure mode is largely irrelevant beyond 4xx vs 5xx, should catch exceptions.

Annotating all the exceptions on the call graph in between is not only pointless, it breaks encapsulation.

binary132 · a month ago
This is a better way of expressing what I had been thinking about putting exception details behind an interface, except that in my mind encapsulating errors is just good design rather than implementation hiding, since the programmer might want to express a public error API, for example to tell the user whether a given fopen failed due to not finding the file or due to a filesystem fault.
1718627440 · a month ago
If your error codes leak the implementation details through the whole call stack you are doing it wrong. Each error code describes what fails in terms of it's function call semantics. A layer isn't supposed to just return this upwards, that wouldn't make sense, but to use it to choose it's own error return code, which is in the abstraction domain of it's function interface.
paulddraper · a month ago
It causes big problems in Java.

For example, it plays poorly with generics. (Especially if you start doing FP, lambdas.)

If Java added union types, it wouldn't be a big deal, but AFAIK this is still a limitation.

JoshTriplett · a month ago
In fairness, Rust also has unchecked exceptions (panics, if you use panic-unwind).
majormajor · a month ago
Java too has unchecked exceptions, though the post praising "mandatory declaration" didn't mention it for either.

But they CAN be caught with a standard catch which may be non-intuitive if you don't know about them, and about that, in advance.

mgaunard · a month ago
Author completely misunderstands how to use exceptions and is just bashing them. A lot of what he says is inaccurate if not outwardly incorrect.

Also Bjarne's control of C++ is quite limited, and he is semi-retired, so asking him to "fix his language" is fairly misguided. It's designed by a committee of 200+ people.

Anyway what you want seems to be to not use exceptions, but monads instead. These are also part of the standard, it's called std::expected.

gblargg · a month ago
Agreed. Author is trying to mix paradigms. Simplest approach if they want local handling and non-propagation of errors is to just have the file holder not check for open success, and check that manually after construction. Then you get guaranteed closure of file no matter how the function is exited.

  class File_handle {
      FILE *p;
  public:
      File_handle(const char *pp, const char *r)  { p = fopen(pp, r); }
      ~File_handle() { if ( p ) fclose(p); }
      FILE* file() const { return p; }
  };
  
  
  void f(string s)
  {
      File_handle fh { s, "r"};
      if ( fh.file() == NULL ) {
          fprintf(stderr, "failed to open file '%s', error=%d\n", p, errno);
          return;
      }
      // use fh
  }

actionfromafar · a month ago
Goes against "Resource acquisition is initialization" though to have objects which exist but can't be used. (I think that's the relevant pattern?)

However, where the language and it's objects and memory ends and the external world begins, like files and sockets... that's always tricky.

mikhael · a month ago
this may lose the value of errno, right?
matu3ba · a month ago
> Author completely misunderstands how to use exceptions and is just bashing them. A lot of what he says is inaccurate if not outwardly incorrect.

Do you mind to elaborate what you believe are the misunderstandings? Examples of incorrect/inaccurate statements and/or an article with better explanations of mentioned use cases would be helpful.

> it's called std::expected

How does std::expected play together with all other possible error handling schemas? Can I get unique ids for errors to record (error) traces along functions? What is the ABI of std::expected? Stable(ish) or is something planned, ideally to get something C compatible?

mgaunard · a month ago
Well, he's using try/catch locally. You're not supposed to handle errors locally with exceptions. The whole point is inversion of control, you manage them at the point where you can do meaningful recovery, which then triggers the cleanup of the whole stack of frames once the exception bubbles up, rather than systematically cleaning up on the normal control flow path.

Regardless, in his example, he could achieve what he wants by wrapping the try in a lambda, and returning either the value from try or nullopt from catch. But clearly, that's just converting exceptions to another error-handling mechanism because he isn't doing it right.

He claimed that not handling an exception causes the program to crash, that's just plain incorrect. To be fair many people use the term "crash" liberally.

std::expected or equivalent is often used with std::error_code, which is an extensible system (error codes are arranged in categories) that among others interops with errno.

imron · a month ago
> Do you mind to elaborate what you believe are the misunderstandings?

Exceptional C++, by Herb Sutter is an excellent resource that explains this. It’s quite outdated these days but the core concepts still hold well.

When done well, most of your code can be happy path programming with errors/invalid state taken care of mostly automatically.

However it’s also very easy not to do this well, and that ends up looking like the suggestions the author of the article makes.

lynx97 · a month ago
Most criticism of C++ comes from people not really into the language. Sure, learning curve might be an issue. But if you are really into C++, there are a lot of things to like about it. At least I do love it. However, only since C++11. Before that, the language felt very strange to me, possibly due to the same effect, I didn't know enough about it.
ranger_danger · a month ago
I'm the opposite. I like C++ but only until C++11. From that point onward, the rules got WAY more complicated, and there's only so much I can/want to hold in my brain at once, I just prefer simpler rules I guess.

Occasionally I do like to use auto or lambdas from C++11 but even then I have to remember more rules for initialization because braces were introduced.

ignition · a month ago
https://godbolt.org/z/363oqqKfv

  struct File_handle {
      static auto Create(std::string const & pp, const char *r) -> std::expected<File_handle, std::error_code> {
          auto p = fopen(pp.c_str(), r);
          if (!p) return std::unexpected(error_code_from_errno(errno));
          return File_handle{p};
      }
      ~File_handle() { fclose(p); }
  private:
      File_handle(FILE * f) : p{f} {} 
      FILE *p;
  };

Deleted Comment

barrkel · a month ago
You probably do want that exception to bubble up, actually. You probably don't want to catch it immediately after open. Because you need to communicate a failure mode to your caller, and what are you going to do then? Throw another exception? Fall back to error codes? Unwind manually with error codes all the way up? And if so, logging was the wrong thing to do, since the caller is probably going to log as well, based on the same philosophy, and you're going to get loads of error messages for one failure mode, and no stack trace (yes, there are ways of getting semi-decent stack traces from C++ exceptions).

Exception safety has a lot of problems in C++, but it's mostly around allowing various implicit operations to throw (copies, assignments, destructors on temporaries and so forth). And that does come down to poor design of C++.

1718627440 · a month ago
> you're going to get loads of error messages for one failure mode

> and no stack trace

That loads of error messages, meaning every layer describes what it tried to do and what failed, IS a user readable variant of a stack trace. The user would be confused with a real stack trace, but nice error messages serve both the user and the developer.

barrkel · a month ago
This is error-prone boilerplate that obscures the code, obscures the logs and is a known antipattern (log and throw) - which you're implementing manually, by hand, in the hope you never make a mistake.

You shouldn't do manually what you can automate.

Boilerplate can make you feel productive and can give you warm fuzzies inside when you see lots of patterns that look familiar, but seeing the same patterns over and over again is actually a smell; it's the smell of a missing abstraction.

lealanko · a month ago
This is a common problem with try-catch syntax. An alternative, and arguably more useful syntax would be

  try (File_handle fh {s, "r"}) {
    // use fh
  } unless (const File_error& e) {
    // handle error
  }
Where the "use fh" part is not covered by the exception handler. This is covered (in ML context) in https://www.microsoft.com/en-us/research/publication/excepti...

mgaunard · a month ago
Don't use try/catch in the first place; that's where his error lies.
1718627440 · a month ago
That's not his own invention, but an idea introduced by the language founder to sell the idea of RAII and exceptions. He did say, that he would prefer the C version, but then why use C++ in the first place.
febusravenga · a month ago
I think i agree with commenters that article kind-of uses exceptions wrong.

But this shows problems with C++ exceptions, C++ codebases are literred with bad exception usage because "normal" programmers don't get it. Most of didn't get it for long time. So, they are complex enough that their usage is risk.

Anyway, IMO C++ exceptions have two fundametal problems:

* lack of stacktrace, so actually lack of _debugabbility_, that's why people try/catch everything

* destructor problem (that actually there are exceptions tht cannot be propagated further and are lost

pmontra · a month ago
Or do without exceptions for control flow. For example Elixir does have try/catch but it's used very rarely because most functions return tuples with the first element being either :ok or :error. Then we can pattern match it and return an :error to the caller if we have to, possibly bubbling up several levels return after return. Or let the process crash and restart while the rest of the application keeps running. A surprising number of errors eventually fix themselves (API calls, disk space, missing data) when you design the system to attempt more times to complete its tasks. That's not only a characteristic of Elixir and the BEAM languages. You can do it more or less easily on any language. Maybe you need a queue and workers reading from the queue and all it takes to manage them, and BEAM makes it convenient by including most of it.

The page about try/catch explains it well https://hexdocs.pm/elixir/try-catch-and-rescue.html