CXX - Safe interop between Rust and C++
github.com
github.com
One of the biggest problems with typical FFIs is that they not only use the C-ABI but also at some point invoke some C-API so that the type safety features that both involved languages have, but C does not have, are normally lost at the boundary.
Generating the C bindings from a description of the Rust and C++ interface is a really neat idea.
Now can this be used inside a standard C++ project? Could we write pieces of code in Rust to gradually augment a C++ library?
The build system is also confusing but I guess that's partly just because it's a really complicated thing to do.
Still, beats doing it all by hand!
Some easy and efficient ways to return a vector (or similarly string) from Rust to C++ which do not involve moving it around in Rust:
1. A signature like `fn f() -> UniquePtr<CxxVector<T>>` i.e. a return type of std::unique_ptr<std::vector<T>>, which is trivially Rust-movable
2. A signature like `fn f(out: Pin<&mut CxxVector<T>>)` with a stack-allocated empty vector constructed by C++ for your Rust to write the elements into, which is pinned and never moved by Rust
3. A signature like `fn f() -> Vec<T>` returning a Rust vector i.e. rust::Vec<T> on the C++ side; rust::Vec has a fleshed out API in C++ allowing it to be used like a C++ collection, with iteration and indexing etc
For any C++ project to integrate Rust the motivation would have to be:
- The Rust code is specifically needed for the long-term.
- The FFI-boundary is temporary (i.e. always moving) as more of the C++ is migrated to Rust. Ideally, checkpointing the migration at optimal FFI boundaries. As such, any "in-efficient" checkpoint should be cleaned up "soon", and dev speed is more valuable from an FFI than perfectly zero-cost.
- If for any reason a C++ project integrates Rust in a way that is permanently through an FFI (I can't understand why - maybe there is a really good crate or something), then the development costs to use the unsafe non-ergonomic FFI are worth the effort for this unique situation.
Very similar logic applies for any Rust project that integrates with a C++ library for the long term. If the team just needs a C++ library for the short term, the efficiency likely also doesn't matter much.
1. FFI boundary will likely to exist forever in a milions-of-line C++ codebase, especially when the behavior of this system is not possible to be formally specified / tested (e.g., depending on an unknown external system, or some behaviors specified in hundreds of pages "specs" full of jargons)
2. In the above case, when C++ code dominates the FFI cost would be signified as you need to call C++ routines frequently to achieve stuffs. For example, when every struct has some methods returning std::string the std::string needs to be targeted.
In our case, the primary motivation of Rust isn't its safety - we just use it for syntax sugars and ease of extensions (with proc macros).
Fair enough, but then "CXX — ***safe*** FFI between Rust and C++" (emphasis mine) is just not the right library for you, and that is totally ok. It has a niche and your usecase is different, both the library and your usecase are in "the right".
> The motivations in claims unfortunately don't apply to my case:
Is this situation different than what I called out earlier?
> - If for any reason a C++ project integrates Rust in a way that is permanently through an FFI (I can't understand why - maybe there is a really good crate or something), then the development costs to use the unsafe non-ergonomic FFI are worth the effort for this unique situation.
I don't follow this train of thought; could you elaborate? Why would I want more unsafe code in my project because it's going to be there for longer? If anything I'd want the opposite -- longevity of the unsafe code being a reason against wanting more of it sitting around.
FWIW the primary use case of CXX in my work codebase is in long term hybrid-Rust-C++ projects in which dozens of libraries using CXX (in both directions) are going to be around for many years.
Like with any premature optimization, I was just pointing out that perfectly zero-cost is not always the goal i.e. if it is short lived code, don't worry about the inefficiency in the FFI.
If the FFI code is going to be long lived, and the team has followed all of the rules for optimization (i.e. measure, profile, etc) and CXX cannot support the performance they need, then in this very unique situation CXX might not be the right tool for the job. Instead it can sometimes be a good decision to write custom unsafe code, abstract it, validate it, write tests for it, basically spend X weeks of dev effort on making the custom unsafe code "safe" with the knowledge that it's an investment for the long term.
> Still, beats doing it all by hand!
Still a pain to maintain, tweak and update your Rust project and cargo configs with your C++ code alongside a third party crate maintained by one person in the project. D lang is probably the only language that has this built into into the language.
I'd rather go for a first party library with this support or C++ interop support built into the language like D.
You can also catch C++ exceptions (by turning them into the idiomatic Result type in Rust) using cxx.
I think you want something like this instead: https://crates.io/crates/abi_stable
it maps naturally and efficiently many common features afforded by the C++ language and often considered problematic, including overloaded operators, class and function templates, callbacks through function pointers, function objects (aka functors), virtual functions and member function pointers, nested struct definitions, variable length arguments, nested namespaces, large data structures containing arbitrary cycles, virtual and multiple inheritance, passing/returning by value/reference/string/vector, anonymous unions, bit fields, exceptions, destructors and shared or unique pointers (via either try-with-resources or garbage collection), and documentation comments