FR: Making `extern "C-unwind"` work with `-fno-exceptions`

Quick background: in many C++ compilers, a flag like -fno-exceptions will turn off exceptions and not generate code for unwinding, while -fexceptions (the default) allows for unwinding exceptional control flow. If a Rust function is extern "C-unwind" and panics, it will try to unwind even into C++ callers. This is, as I understand it, Undefined Behavior (UB) if the C++ function which called it was built with exceptions disabled.

(This is unlike extern "C", where it aborts on reaching an FFI boundary, in any case.)

This means that for an extern "C-unwind" function which panics, we have the following compatibility matrix:

  • Works (aborts): -Cpanic=abort, -fexceptions
  • Works (aborts): -Cpanic=abort, -fno-exceptions
  • Works (unwinds): -Cpanic=unwind, -fexceptions
  • Doesn't work (UB): -Cpanic=unwind, -fno-exceptions

I would like to ask, as a FR, that we get some new compilation flag (for the sake of a straw example: -Cpanic=unwind-no-exceptions) which makes a panic abort when passing through extern "C-unwind", the same as if it passed through extern "C". This would make it defined behavior to call from C++ when built without exceptions, and doesn't require modifying the Rust source code to use extern "C".

I want this as a compiler flag because, otherwise, all library code which tries to drop in place for a C++ library, and uses extern "C-unwind", must make itself configurable (via features or similar) so that it is extern "C" if the project building it needs it to not unwind. Any which do not will be dangerous to use in an environment where Rust has unwinding panics but C++ might not have exceptions. It seems to me to make sense to solve this once in a central place, as a build flag.

(I'm talking only about C++ here because I have no idea what happens with C interop!)



By the way, I think maybe the most obvious question one might ask is, "why would you enable panics in Rust but not enable exceptions in C++? Why not use -Cpanic=abort?"

Firstly, I think this is kind of common: if you build C++ without exceptions, pretty much every language you FFI with will have unwinding control flow. If you're treating Rust as an independent language, it just sort of happens.

Separately, Rust panics might be considered fundamentally safer than C++ exceptions: with things like mutex poisoning, the UnwindSafe trait, etc., the cost/benefit of enabling unwinding control flow is different in Rust and C++.

But in my view, the most interesting reason is that, C++ codebases are sometimes burdened by a technical debt to the past, where Rust codebases are new, and can take on new patterns. For example, the Google C++ style guide, which bans exceptions, says::

On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. [...] Things would probably be different if we had to do it all over again from scratch.

It seems reasonable to enable unwinding panic in a newer, more isolated subcomponent like Rust code, where it's possible, while terminating the unwinding control flow where it enters older, existing C++ code which isn't exception safe.

3 Likes

If I understand it right, this is essentially a flag that says "treat "C-unwind" like "C"".

The only thing I'm not sure about there is, it might be defensible to want C-unwind to only look like C for panics, and not for exceptions, if you have a rust library which is linked against both C++ code with and without exceptions enabled. I don't, and that seems like a bad idea, but maybe someone does.

Sounds reasonable. That would delegate the responsibility of not unwinding through non-unwind code to the respective codebase where the unwinding originates. (Which could be a footgun, but would make it consistent with the case of two C++ libraries with different exception flag linked together without Rust code inbetween.)

But then the name -Cpanic=unwind-no-exceptions would be misleading and needs to be bikeshedded.