> Writing an FFI for some C++ so that C code may call it takes almost as much work as doing the same for Rust
where "as much work" means putting `extern "C"` in front of your functions and having a few `static_assert(is_pod_v<type_that_i_pass_in_argument_to_my_c_api>)` or only using opaque typedefs, ie it frankly amounts to nothing
No, as much work means that you can't use 99% of C++ in C FFI. You have to fully instantiate template functions, template structs (you can pass `std::vector` to C FFI), etc. before you can expose them to C.
If you are writing C++ in such a way that this does not matter, then you are basically writing C already and `extern C` is all you have to do though, but then you might as well just have been using C this whole time.
having done this process for multiple libraries using a lot of fancy C++ >=14 features, I really think that it's not a big deal - at most an afternoon of repetitive work if you have an API with ~100 functions.
If that is all you need, then Rust has bindgen/cbindgen that does the same.
But when your API uses templates/generics and other types that C can't comprehend, you need to write additional wrapper functions operating behind opaque C types. It's not rocket science, and tooling/macros can help a lot, but it's still tedious and error prone (you're erasing types, redoing error handling, adding manual memory management, etc.).
where "as much work" means putting `extern "C"` in front of your functions and having a few `static_assert(is_pod_v<type_that_i_pass_in_argument_to_my_c_api>)` or only using opaque typedefs, ie it frankly amounts to nothing