Because Rust offers compelling advantages over C/C++, a company might decide to migrate its software product to Rust. A question this often raises is how such migration can be achieved gradually, as there are often a few constraints:
- A complete rewrite of the code is not feasible.
- The team first has to get used to Rust.
- Our product is compiled as a single binary.
This leaves the option of integrating a Rust library into an existing C/C++ using a Foreign Function Interface (FFI). That way, we can gradually introduce Rust into our product.
Goal: a Rust library with a C++ look
We have an existing C++ project, which consists of a number of different libraries. We now want to write one of these libraries in Rust and use it in C++.
- From the outside, the Rust library should behave like a C++ library.
- It should be easy to integrate the Rust library into the existing CMake project so that Rust-specific characteristics remain encapsulated.
- We want to reuse the Rust library later on in a pure Rust context. In other words, we don’t want FFI code in the Rust library.
Quiz
Test your knowledge of Rust!
Approach: building a bridge with C-ABI
We develop a pure Rust library with its tests and encapsulate this library in a wrapper library so that it can be used from C++.

For C++ to use Rust functions in the first place and vice versa, we use the C Application Binary Interface (C-ABI). This is because only the C-ABI is stable and can therefore be used by both languages. Neither the C++ ABI nor the Rust ABI is stable.
Communication between Rust and C++ can be illustrated using a bridge analogy:

The C-ABI builds the bridge. The C++ wrapper translates function calls into C function calls and sends them across the bridge. The function calls are passed on to the corresponding Rust functions on the Rust side.
Implementing the C-ABI bridge
On the C++ side, we make the Rust library available to a class as a C++ header file. We make sure that only meaningful operations can be performed on objects of this class. The copy constructor and the copy assignment constructor are therefore deleted in this class, because these operations are not supported on the Rust side. This class has only one pointer (of an opaque type) to the actual object (a unique_ptr to the object is also possible, thought the deleter has to be implemented independently). In addition, an object can only be instantiated using a factory function (New).

The signature of the bridge function (in the external C block) is required to implement this class. The factory function “New” passes its arguments (with a few conversions) across the bridge (person_new(..)). The return value from person_new() is a pointer to the object that was created in Rust. The C++ object is constructed with this pointer as a member variable.
We have to implement the destructor manually so that the wrapper class can meaningfully manage the useful life of the underlying Rust type. A function call over the bridge is needed for this.

The C-ABI bridge functions are defined on the Rust side of the bridge. The bridge functions are normal Rust code, though the function signature of the C-ABI suffices. In the case of the person_new() function, the arguments are converted in turn and the factory function of the Rust struct then called. This factory function (Person::new(..)) comes from the pure Rust world. The Rust struct is allocated on the heap and a raw pointer to it is passed back to the C++ world.

C++ and Rust are now connected via the C-ABI bridge. The other functions that interact with the Rust object are implemented in the same way as described above.
This approach allows us to achieve the defined objectives of a cleaner encapsulation of the Rust code and its seamless integration into a C++ project.
Will Rust soon replace C++?
The race between programming languages
Alternative to the C-ABI bridge: cbindgen
The procedure described aims for strict encapsulation of the Rust and C++ world. The disadvantage is that we have to manually write the C function signatures in the C++ implementation.
If less strict encapsulation of the code is acceptable, Rust functions and Rust structs can also be made available directly for use in C++. The function signatures can be generated in this case with the tool cbindgen.
Alternative – or: just keep using C
If Rust is to be integrated into a C project, the option of creating a wrapper class no longer applies. The process is made simpler because the bridge functions imported from Rust can be used directly. From a technical perspective, there is no problem with this. However, the status is no longer automatically managed by the wrapper class, rather has to be managed manually by the code used.

Rust Transition Service
Securely into the future
Build system: it’s the line of code that matters
In terms of the build, we also expect to be able to easily integrate the Rust library into a C++ project.
From the perspective of the top-level project, the Rust library is added like any other static library with two lines of CMake code:
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/crates/person-cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE person_bridge_cpp)
Corrosion is a useful tool for integrating a Rust library into CMake. Corrosion is essentially a collection of scripts, which call Rust’s build tool and make the artefacts produced usable for CMake.
This function does all the hard work, with the Rust project being built and imported into CMake:
corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml)
The wrapper library is created with these two lines and the Rust library built with Corrosion is linked to it:
add_library(${PROJECT_NAME} STATIC src/lib.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE person_bridge_rs)
The C++ header file is made available to the consumers:
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
Conclusion: a little more effort but a leaner architecture
There are various approaches to integrating a Rust library into a C/C++ project. Using the procedure described above, we achieve our goals of seamlessly integrating a Rust project. However, the two parts remain separate and only communicate via well-defined interfaces. Although this requires a little more effort, it results in a lean architecture, which pays off in the long term.

The expert
Oliver With
Oliver With is an expert in embedded software. As a senior developer, he is convinced that the best way to solve complex problems is by teams working closely together. He combines creative approaches to finding solutions with high-quality development to create successful products. He is a Rust enthusiast, because for the first time Rust provides a language that combines security, performance, industry acceptance and ergonomics for developers.