Bridge builder

How to integrate Rust into C/C++ projects

It’s easy to assume that Rust and C, or even C++, are not compatible. Our Rust expert Oliver With explains in this article how they can be – and how this can be used to future-proof and streamline a project.

02.06.2025Text: Urs Häfliger0 Kommentare
Header Blogserie Rust Brueckenbauer C zu Rust

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.
Rust Prgramming Language Quiz
Quiz

Test your knowledge of Rust!

Rust is winning the hearts of developers – perhaps yours too. But how knowledgeable are you about Rust? Very? Then put your knowledge to the test!
To the quiz

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++.

Bild1 Blogserie Rust Brueckenbauer
Bild1 Blogserie Rust Brueckenbauer

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:

Bild2 Blogserie Rust Brueckenbauer
Bild2 Blogserie Rust Brueckenbauer

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).

Bild3 Blogserie Rust Brueckenbauer
Bild3 Blogserie Rust Brueckenbauer

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.

Bild4 Blogserie Rust Brueckenbauer
Bild4 Blogserie Rust Brueckenbauer

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.

Bild5 Blogserie Rust Brueckenbauer
Bild5 Blogserie Rust Brueckenbauer

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.

Eine Rennstrecke mit zwei Motorradfahren im Vordergrund, die sich ein Duell liefern, und weiteren Fahrern im Hintergrund
Will Rust soon replace C++?

The race between programming languages

C++ has dominated the programming world for decades, but Rust is gaining ground. Find out in this blog post who is likely to win.
Learn more

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.

Bild6 Blogserie Rust Brueckenbauer
Bild6 Blogserie Rust Brueckenbauer
Rust Transition Service

Securely into the future

Software development is changing rapidly. Our Rust Transition Service is dedicated to making your company’s software secure, efficient and future-proof.
Explore now

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. 

Simple handling

Rust: Tools for reliable dependency management

Rust
Prepared for the change

Successfully introducing Rust into your team

Rust
The most important IT trends for Swiss SMEs in 2025

“Not all trends make it into the bbv Technica Radar”

Digital Transformation

Attention!

Sorry, so far we got only content in German for this section.