Configure the crate's required panic strategy

Some crates use the panic mechanism by design in normal situations, not just for irrecoverable errors. Linking them to the panic_abort runtime is a logic bug.

Crates may try to prevent this situation with #[cfg(panic = "abort")] compile_error!(...);, but that is insufficient. cargo rustc -- -C panic=abort successfully compiles dependency crates with the default panic runtime (supposedly panic_unwind) while linking the binary crate to the panic_abort runtime, producing broken executables if the dependency crate depends on unwinding. This works even on stable.

There is currently a hacky way to cause rustc to emit a compile-time error in this case: if the dependency crate ever calls an extern function with an unwinding ABI:

unsafe extern "C-unwind" {
    safe fn f();
}

f();

rustc errors out like so:

error: the crate `<dependency crate>` requires panic strategy `unwind` which is incompatible with this crate's strategy of `abort`

However, this is an indirect approach that needs a globally universal identifier (instead of f) and does not really express the intention well.

It is also possible (and IMO realistic) for some crates to only work correctly under the panic_abort runtime. For example, a hypothetical replace_with crate could provide an implementation that would only be sound without unwinding. In this case, however, #[cfg(panic = "unwind")] compile_error!(...); is a sufficient check, as any crate being compiled with the aborting runtime is sufficient to make the whole executable panic_abort.

Finally, some crates might want to choose between two implementations (e.g.: raising very rare, but recoverable errors with panics or via Results) depending on the panic runtime. Such crates would always work correctly if linked with panic_abort, but panic_unwind would be more efficient. The desired effect here is for code behind #[cfg(panic = "unwind")] to not be linked to the panic_abort runtime, but for -C panic=abort to work nevertheless if it's applied to the whole dependency tree rather than just the final executable.

I think that it'd be useful for this to be unified and configured with a single knob. Cargo.toml is the obvious choice to place it in. There's already a panic field in [profile.*], but the intention here is to configure the required behavior rather a recommended behavior, i.e. semantics rather than optimization settings, so this strikes me as a bad choice. Perhaps package.required_panic_strategy would work better. That option would accept any of the following four values:

  • none (default): No additional requirements, behavior matches current Cargo.
  • unwind: Fail linkage if panic_unwind isn't linked in. Equivalent to the extern "C-unwind" trick, used by crates that always need panics.
  • abort: Fail linkage if panic_abort isn't linked in. Equivalent to #[cfg(panic = "unwind")] compile_error!(...);, used by crates that assume unwinding is impossible for soundness.
  • same (bileshed): Fail linkage if the runtime the crate is compiled with doesn't match the runtime that is linked in, used by crates that need to change code in compile-time depending on the panic runtime that is linked in.

Thoughts? Vibes?

2 Likes