C++ Logo

std-proposals

Advanced search

Re: [std-proposals] fine-control exception/error/UB handling on function boundary

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Sat, 29 Aug 2026 13:00:10 +0200
sob., 29 sie 2026 o 10:42 Tiago Freire <tmiguelf_at_[hidden]> napisał(a):
>
> This is exactly the trap I've seen in production code over and over again.
>
> From my experience code that uses exceptions is more unstable (unexpectedly crashes), and 9 times out of 10 the reason for that instability is unhandled exceptions.
> You throw a new exception and you think you don't have to care, and that it somehow it all gets solved by magic. But somewhere up the call stack someone has to, and it is really hard to find out where that is.
> Every time you throw a new type of exception you incur a debt that has to be paid.
>

Could you give me a real world example of a crash like this? What
happened and how you should handle this exception?
Because in many cases top `catch(...) { logException(); cleanState();
}` is enough to not crash the whole program.
And if this is a user initiative program it anyway needs his action to
solve the problem.

> You can always argue "I don't have to care here, I don't have to care there" and you can squint your eyes and say "yeah...ok", but then make the mistake of thinking that you "don't have to care anywhere".
> That's how bugs of this sort often get introduced. This is precisely the kind of thing that should pop up in a code review and get the comment "hey can you not introduce a new exception and just use std::exception [like always]?" or "have you validated that all code upstream is prepared to receive this exception?"
>

In one program I work with, catching exceptions is primary only to add
context to errors. This means when I fail to handle exceptions it does
not change correcens of the program. Only thing affected is if the
user gets a bad error message or a good one for the error he did.
Of course diffrent programs or even parts of the same program have
diffrent requirements, this is why I ask you for concrete examples to
better understand your case.

>
> -----Original Message-----
> From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
> Sent: Saturday, August 29, 2026 00:33
> To: Tiago Freire <tmiguelf_at_[hidden]>
> Cc: marcinjaczewski86_at_[hidden]; std-proposals_at_[hidden]; Henry Miller <hank_at_[hidden]>; Zamfir Yonchev <zamfir.yonchev_at_[hidden]>
> Subject: Re: [std-proposals] fine-control exception/error/UB handling on function boundary
>
> pt., 28 sie 2026 o 20:52 Tiago Freire <tmiguelf_at_[hidden]> napisał(a):
> >
> >
> > > If a function already throws an exception, does this matter what new type it throws for the whole call stack?
> > > Only place where it matters is the place where you handle this new exception, rest code should stay agnostic to this new error case.
> >
> > Yes, it does matter. How else are you going to find out the "Only place[s] where you handle this new exception"?
> > That's a much harder problem with exceptions than just looking up who is calling your function.
>
> In both cases you need to scan whole call tree in both cases anyway, and in case of error codes you need to modify lot of more code to make it work.
>
> Consider simple function:
>
> ```
> void foo(auto& data, auto& callback)
> {
> for (auto& d : data)
> {
> callback(d);
> }
> }
> ```
>
> Its already used in contexts where callback can break this loop by throwing. Will adding 10 new exceptions there change anything?
> Code could be fully agnostic to exception handling.
>
> Lest see another function:
>
> ```
> auto bar(auto& callback)
> {
> int i = 0;
> while (true)
> {
> try { return callback(); } catch (Timeout&) { if (++i > 10) throw; Wait(2); }
> }
> }
> ```
>
> Does this function need to know about any new exceptions? No unless is equivalent to `Timeout` and there is a point to repeat operation.
> If this is the case then that function is the place where we handle new expeditions otherwise we can ignore it.
>
> In return codes you need to update whole call stack to propagate a new error type.
>
> >
> > > Error codes are the same
> >
> > They most certainly are not. The day you find a function were adding an error code silently adds a new return type to the caller without so much as a warning, is the day I will quit programming.
> >
>
> We have opposite case, change `void Baz()` into `Error Baz()` compiles in majority cases without any errors.
> You need to add attributes to prevent this.
>
> > > Error codes are the same, if you need to add failable operation to any function you need to add propagation of failure upstream.
> >
> > Do you?
> >
> > > If the function returns `void` you need to change it to return some error code.
> >
> > Yes!
> > And the problem is?
> > You changed the contract. Something that used to not fail, now does. If the code was not prepared to handle that, it should fail to compile and force you to fix the caller.
> > I don't see "having to do more work to fix other places because the interface has changed" as a problem, the problem is having to do all that and not know, and bugs are created and left to rot your code base.
> > That's why I think it would be amazing in code bases that used exceptions if functions were forced to document what exceptions they could throw. You would "catch" the problem immediately instead of staying undiscovered until one day they come to bite you.
> >
>
> In most cases it only matters if it throws or not, what is thrown is a secondary concern. This is why C++ added `noexcept`.
> Probably some new attributes that work similar to one that prevent ignoring return values could be added to enforce adding `noexcept(true)` and `noexcept(false)` to more functions.
>
> >
> > > Even better if you have robust error codes of 20 diffrent values and add some C code that has its own error codes how do you map both? Do you glue them together?
> >
> > Are you making an argument for return codes? If function X returns an error code in the standard A, but it calls function Y that returns an error code in standard B, if Y adds a new error to B, and X wants to propagate that extra detail to the caller then it must have to convert it to standard A, so that the caller of X only ever has to deal with errors in standard A.
> > Exceptions is where you have to deal with the mess of having to deal with errors in standard A and standard B, or whatever C D or E may occur in the future, which you have no way to know because the interface doesn't document that.
> >
> > You wouldn't do this:
> > ```expected_variant<int, ErrorA, ErrorB, ErrorC> Foo(); ```
> >
> > You just do this:
> > ```expected_variant<int, ErrorA> Foo(); ``` ever
> >
> > But
> > ```int Foo() throw(ErrorA, ErrorB, ErrorC); ``` Is what you get with
> > exceptions, because the caller doesn't have to deal with the exceptions.
>
> If you can easily "collapse" error codes into one then even easier you can do the same for exceptions.
> And catch all libs exceptions and convert them into yours at first ocasion. And at end of your function have:
> ```
> catch(...)
> {
> throw UnexpectedException("From function foo"); } ``` And you can make some reasonable handling of this case in the rest of the call stack.
> Most cases simply ignore it and allow it to bubble up to the top function where you report to the user or cancel the whole operation.
>
> > But it is made much worse if you do this:
> > ```int Foo(); ```
> > You have to catch ErrorA, ErrorB, and ErrorC, but it doesn't tell you.
> >
> > Do you see the problem?
> >
>
> This is more of a tooling problem than langage problem. As I said before you can create a tool that will say if you handle all exceptions thrown by function and you don't even need source code to do this as binary needs to have this information available for unwinding.
> Some new attributes that at link time check this could be useful.
>
>
> >
> > -----Original Message-----
> > From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
> > Sent: Friday, August 28, 2026 20:02
> > To: std-proposals_at_[hidden]
> > Cc: Henry Miller <hank_at_[hidden]>; Tiago Freire
> > <tmiguelf_at_[hidden]>; Zamfir Yonchev <zamfir.yonchev_at_[hidden]>
> > Subject: Re: [std-proposals] fine-control exception/error/UB handling
> > on function boundary
> >
> > pt., 28 sie 2026 o 18:33 Tiago Freire via Std-Proposals <std-proposals_at_[hidden]> napisał(a):
> > >
> > > > the real problem is as soon as you make that change you have often thousands of functions that cascading need to change because now you changed your interface.
> > >
> > > Yes, but isn't that just the symptom of a much larger problem.
> > > You haven't as much provided a useful function as you have provided a means for your application to ungracefully terminate if you don't document that your code can throw, what it can throw and how you can recover if a function does throw. I.e. you just gave everyone a loaded foot gun.
> > >
> > > If you state that your function can throw A, B, and C I can write a catch clause to catch A, B, and C and *handle the error*.
> > > But now you if you make a change and it can also throw D, the fact that this cascades trough out your entire code base is a good thing.
> > > You have changed the contract of what the function can do, code that was previously safe now isn't, and if the caller doesn't catch it you have also changed the caller's contract, then the caller of the caller will throw an additional exception as well.
> > >
> >
> > If a function already throws an exception, does this matter what new type it throws for the whole call stack?
> > Only place where it matters is the place where you handle this new exception, rest code should stay agnostic to this new error case.
> >
> > > Exceptions are evil because it changes code upstream instead of downstream.
> > >
> >
> > Error codes are the same, if you need to add failable operation to any function you need to add propagation of failure upstream.
> > If the function returns `void` you need to change it to return some error code. If it's already return some error code you need extend it to add new value and handle it upstream. Even better if you have robust error codes of 20 diffrent values and add some C code that has its own error codes how do you map both? Do you glue them together? Even is only place where this can happen?
> >
> > Only way to handle it generally is code like:
> >
> > ```
> > expected_variant<int, ErrorA, ErrorB, ErrorC> Foo(); ```
> >
> > That have exactly same problems as
> >
> > ```
> > int Foo() throw(ErrorA, ErrorB, ErrorC); ```
> >
> >
> >
> > > The fact that you have to deal with changing code in a thousand different places throughout the code base, is a small mercy that allows you the opportunity to at least validate if code upstream was able to handle it.
> > > Make that change silent and you have not fixed the problem, you just hid it and don't realize you created a problem, now you have thousands of different bugs with a single code change.
> > >
> > > That's why I'm in the camp "No exceptions" please!
> > >
> > > -----Original Message-----
> > > From: Henry Miller <hank_at_[hidden]>
> > > Sent: Friday, August 28, 2026 14:21
> > > To: Henry Miller via Std-Proposals <std-proposals_at_[hidden]>
> > > Cc: Tiago Freire <tmiguelf_at_[hidden]>; Zamfir Yonchev
> > > <zamfir.yonchev_at_[hidden]>
> > > Subject: Re: [std-proposals] fine-control exception/error/UB
> > > handling on function boundary
> > >
> > >
> > > On Fri, Aug 28, 2026, at 03:38, Tiago Freire via Std-Proposals wrote:
> > >
> > > > And I would kill to be able to specify a list of possible
> > > > exceptions at the interference level to be able to validate the
> > > > code is exception safe.
> > > >
> > >
> > > A number of languages have tried that in the past and books have always quickly come up and said, never specify the exact exceptions except on a very generic level. That is, you say, 'throws anything' in whatever the language syntax specifies that as. Too many times you want to make a slight change to implementation and discover that it would really be helpful to throw a different exception in some situation. Often some users are demanding a specific bit of information that they would catch and handle differently. the real problem is as soon as you make that change you have often thousands of functions that cascading need to change because now you changed your interface. You can throw a clang tidy at the problem but that's not going to tell you the place where you should catch the exception in cases where you don't want to cascade it all the way and so it's a question of valuable even with modern tools.
> > >
> > > Now in C++ it would be useful to say throws std::exception, that is I won't throw an int or any other type that is not derived from standard exception. This is good practice anyway. It might be useful to also say, we'll not throw std::bad_alloc or something like that, where you can say a whole class of things would not be thrown.
> > >
> > > Though I'm not sure if there's any advantage to putting the above into the language versus putting it into something like the doxygen comments. I'm not sure if the compilers can use it and so long as the information of what your wheeler will throw is in place, static analysis can find it, that's probably good enough for your needs.
> > >
> > > however specific list of exceptions that a function will throw is
> > > always going to be listed in every intelligent style guide we will
> > > not use this
> > > --
> > > Std-Proposals mailing list
> > > Std-Proposals_at_[hidden]
> > > https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2026-08-29 11:00:28