Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Leon Matthes,
Andrew Hayzen
1 August 2024
In our earlier blog, The Smarter Way to Rust, we discuss why a blend of C++ and Rust is sometimes the best solution to building robust applications. But when you’re merging these two languages, it’s critical to keep in mind that the transition from C++ to Rust isn’t about syntax, it’s about philosophy.
If you’re an experienced C++ developer who is new to Rust, it’s only natural to use the same patterns and approaches that have served you well before. But problem-solving in Rust requires a solid understanding of strict ownership rules, a new concurrency model, and differences in the meaning of “undefined” code. To prevent errors arising from an overconfident application of instinctual C++ solutions that don’t align with Rust's idioms, it’s a good idea to start by tackling non-critical areas of code. This can give you room to explore Rust's features without the pressure of potentially bringing critical systems crashing down.
Performance is often a concern when adopting a new language. Luckily, Rust holds its own against C++ in terms of runtime efficiency. Both languages compile to machine code, have comparable runtime checks, don’t use run-time garbage collection, and share similar back-ends like GCC and LLVM. That means that in most real-world applications, the performance differences are negligible.
However, when Rust is interfaced with C++ via a foreign function interface (FFI), there may be a noticeable overhead. The FFI disrupts the optimizer’s ability to streamline code execution on both sides of the interface. Keep this in mind when structuring your hybrid applications, particularly in performance-critical sections. You could use link-time optimization (LTO) in LLVM to help with this, but the additional complexity of maintaining this solution makes it a consideration only if profiling/benchmarking points to FFI being a main source of overhead.
The normal approach for Rust is to eliminate code marked as ‘unsafe’ as much as possible. While both C++ and ‘unsafe’ Rust allow for pointer dereferencing that can potentially crash, the advantage in Rust is that this keyword makes issues easier to spot. ‘Unsafe’ Rust pinpoints where safety compromises are made, highlighting manageable risk areas. This in turn streamlines code reviews and simplifies the hunt for elusive bugs.
Connecting Rust and C/C++ is clearly required when building hybrid applications. Thankfully, there’s a rich ecosystem of tools to support this:
Each tool serves a distinct purpose and choosing the right one can make the difference between a seamless integration and a complicated ball of compromises. (We provide more detailed guidance on this topic in our Hybrid Rust and C++ best practice guide, and my colleague Loren has a three part blog series that talks about this topic too: part 1, part 2, part 3.)
In complex applications with interwoven parts, it's probably best to keep Rust and C++ worlds distinct. Taking a cue from the microservices design pattern, you can isolate functionalities into separate service loops on each side, passing data between them through well-defined FFI calls. This approach circumvents issues of thread blocking and data ownership, shifting focus from direct code calls to service requests.
Rust does not guarantee a stable ABI between releases, which influences how you must design and compile your applications. To prevent breaking the build, create statically linked executables or use C FFIs for shared libraries and plugins, ensure that your entire project sticks to a consistent Rust version, and encapsulate all dependencies behind a C FFI.
Building hybrid applications requires a thoughtful approach to choose a build system that will work well for the code you have and adapt easily as your program evolves.
If you’re considering a custom build system, closely examine the available options first. With the breadth of today’s build tool landscape, it’s usually overkill to invent your own solution.
By understanding the challenges and employing the right strategies, C++ developers can smoothly transition to Rust, leveraging the strengths of both languages to build robust and efficient applications. For more information on this trending topic, you’ll want to consult our Hybrid Rust and C++ best practice guide, which was created in collaboration with Ferrous Systems co-founder Florian Gilcher.
About KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Learn Rust
In collaboration with our partners Ferrous Systems, KDAB provides a variety of introductory and advanced training courses for the Rust language.
Learn more
Learn Modern C++
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.
Learn more