C++ inheritance doesn't map well to Rust's trait system for one. I think this is an ongoing issue within the `bindgen` community, but I don't know much beyond that. Suffice to say, some concepts in C++ do not map well to Rust and so FFI can be hairy in some instances.
On instance of this becoming a complication would be in the GTK+ world with the Rust bindings to GObject. There's some really good articles about their adventures in that realm you could find.
Writing an FFI for some C++ so that C code may call it takes almost as much work as doing the same for Rust. That's less true the closer your preferred dialect of C++ is to C, I suppose.
Meanwhile Rust, in this user's opinion, offers a superior toolset for modeling data and building behavior, namely: sum types and traits.
So if you're looking to extend or integrate with a C codebase, barring some exotic target platform and all else equal, I don't know why you'd opt for C++ over Rust.
> 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.).
Rust interfaces very well with C. When you have a large C codebase it's easy to one day just stop writing any more C, and write all new code in Rust without skipping a beat. There are successful examples of this like librsvg.
OTOH interfacing with C++ is very hard. D might be the only language that has seriously tried it, but from Rust's perspective C++ has largely overlapping, but incompatible set of features, and it's a weird dead-end.