cpp_to_rust automatically creates Rust crates that provide API to C++ libraries.
cpp_to_rust supports many language features, but some important features are not implemented yet, so it's totally not production-ready (see this section for more details).
cpp_to_rust mainly targets Qt libraries and uses some Qt-specific workarounds, but it may be able to process other C++ libraries fairly well. Some of Qt-specific functionality is separated to qtbuildtools.
The generation process is executed by the build script. It includes the following steps:
cpp_to_rust, information collected during their generation is loaded and used for further processing.clang C++ parser is executed to extract information about the library's types and methods from its header files.enums and structs for all found C++ enums, structs and classes (including instantiations of template classes).cargo build everything together into a single crate.cpp_to_rust's processing data are used to generate a full-featured documentation for the crate (example).Many things are directly translated from C++ to Rust:
bool) and types provided by libc crate (like libc::c_int).int8_t or qint8) are mapped to Rust's fixed size types (e.g. i8).Drop and CppDeletable implementations.QFlags<Enum> types are converted to Rust's own similar implementation.static_cast and dynamic_cast are available in Rust through corresponding traits.Names of Rust identifiers are modified according to Rust's naming conventions.
When direct translation is not possible:
Not implemented yet but planned:
typedefs to Rust type aliases.Class1<T>::Class2.Not planned to support:
Linux, OS X and Windows are supported. cpp_to_rust is continuously tested on the following platforms and targets:
libclang-dev ≥ 3.5 (CI uses 3.8 and 3.9).make and a C++ compiler compatible with the Rust toolchain in use. On OS X, the command line developer tools are required, but full Xcode installation is not required.Crates generated by cpp_to_rust can be added to your project as dependencies, just as any other library crates. We maintain the following crates:
(eventually all Qt libraries will be added).
If you want to run cpp_to_rust on another library of your choice, you need to create a crate, set up a build script and write that build script. An example build script is available in testassets/ctrt1/crate/build.rs file. You can also use the crates listed above as examples. You also need to write a specific include macro in lib.rs file of the crate: see testassets/ctrt1/crate/src/lib.rs for example.
cpp_to_rust's own API is not stable yet and will certainly change a lot.
cpp_to_rust reads the following environment variables:
CLANG_SYSTEM_INCLUDE_PATH - path to clang's system headers, e.g. /usr/lib/llvm-3.8/lib/clang/3.8.0/include. May be necessary to set if clang can't find system headers. Adding this directory as include directory via build script has different effect and may not be enough.CPP_TO_RUST_CACHE - path to the cache directory. If set, cpp_to_rust will use this directory to cache various data and will skip some processing steps if they were cached. This directory can and should be the same for all crates that are processed together. Make sure to clean or change the cache directory if something changes (e.g. cpp_to_rust's version, toolchain, C++ library version, etc.).CPP_TO_RUST_QUIET - if set, turns off warning and debug log levels.C++ build tools and the linker may also read other environment variables, including LIB, PATH, LIBRARY_PATH, LD_LIBRARY_PATH, DYLD_FRAMEWORK_PATH. cpp_to_rust may add new directories to these variables, but it can't manage environment of the final linker, so you may need to set them manually.
cpp_to_rust takes advantage of Rust's crate system. If a C++ library depends on another C++ library, generated Rust crate will also depend on the dependency's crate and reuse its types.
Documentation is important! cpp_to_rust generates rustdoc comments with information about corresponding C++ types and methods. Overloaded methods have detailed documentation listing all available variants. Qt documentation is integrated in rustdoc comments.
cpp_to_rust supports two allocation place modes for C++ objects. The user can select the mode by passing AsBox or AsStruct as additional argument to constructors and other functions that return objects by value in C++.
AsBox mode:
new. Constructors are called like new MyClass(args), and functions that return objects by value are called like new MyClass(function(args)).new is passed through FFI to the Rust wrapper and to CppBox::new. CppBox<T> is returned to the caller.CppBox is dropped, it calls the deleter function, which calls delete object on C++ side.CppBox and passed to another function that can take ownership of the object.AsStruct mode:
new(buf) MyClass(args), where buf is pointer to the struct created in Rust. The struct is filled with valid data.Box or Vec, pass it to another place and take references and pointers to it. AsBox is a more general and safe way to store C++ objects in Rust, but it can produce unnecessary overhead when working with multiple small objects.
AsStruct is more limited and dangerous. You can't use it if pointers to the object are stored somewhere because Rust can move the struct in memory and the pointer can become invalid. And sometimes it's not clear whether they are stored or not. It is also forbidden to pass such structs to functions that take ownership, as they would try to delete it and free the memory that is managed by Rust. However, AsStruct allows to avoid heap allocations and can be used for small simple structs and classes.
Selecting one of these modes in currently up to the caller, but some kind of smart defaults and limitations may be implemented in the future.
Assuming that the C++ library has exactly the same API on all platforms, the generated C++ and Rust code is almost totally portable. The only issue is using sizes of classes because they depend on the platform.
However, in reality C++ libraries often have API differences on different platforms. Even if they are subtle, they can result in changing types and methods in Rust API and consequent build issues in applications that use the crate. Using another version of the C++ library will also cause immediate issues because a C++ wrapper uses every possible function of the library and will definitely fail to build if any functions are missing.
Until these issues are resolved, cross-platform use of generated crates is significantly limited. One possible approach is described here. It would allow to generate code that works on any supported platform and doesn't even require to install clang parser.
C++ wrapper functions may only contain C-compatible types in their signatures, so references and class values are replaced with pointers, and wrapper functions perform necessary conversions to and from original C++ types. In Rust code the types are converted back to references and values.
If Rust crates and C++ wrapper libraries are all built statically, the linker only runs once for the final executable that uses the crates. It should be able to eliminate all unused wrapper functions and produce a reasonably small file that will only depend on original C++ libraries.
However, C++ wrapper libraries are currently built dynamically for MSVC because its linker fails to process so many functions at once. This results in larger executable size and additional DLL dependencies.
Contributions are always welcome! You can contribute in different ways:
cpp_to_rust itself or any of wrapped libraries;