Integrating Rust into a Conan/CMake C++ Project: A Practical Guide
Do not proceed with a mess, messes just grow with time.
– Bjarne Stroustrup
No, this post will not be a rant against C++, sorry
Albeit being a very bloated language, C++ is fast, efficient and will teach you a lot of systems concepts in depth, like memory management and other lower level “spells” that other languages tuck away from it users knowledge. Learning C++ will only make you a better systems engineer.
On the other hand, Rust is known for its safety guarantees, modern language features and - the most important aspect IMO - its wonderful tooling and ecossystem.
So, we should rewrite everything in Rust, right?
– Average person after 3 days in Rust.
No, absolutely not. The Rewrite in Rust culture is a meme. You can write perfectly safe C++ code using later standards (like c++20), modern language features like smart pointers and move semantics. We also have initiatives like CppCoreGuidelines that helps people writing idiomatic and modern cpp.
Tooling still a problem in the C++ ecossystem tho. By the very heterogeneous user base nature o C++, we don’t have any form of tooling consensus in the community, instead, we have a lot of different solutions that aims at different user bases. Probably you can get away with Cmake + Conan.
C++ is more well suited for a lot of tasks, e.g. gamedev and graphics programming, so I’ll choose it over alternatives every time. But, in other areas it doesn’t have a nice ecossystem, like GUI frameworks. My previous attemps on mainstream C++ Gui Toolkits was very painful ones. Take Qt requiring you to learn ANOTHER BUILD SYSTEM for instance and GTK, well, being just GTK.
Moreover, the Gui scene in Rust is wonderful, with Tauri being the most popular framework in this context. Tauri makes writing cross-platforms apps a breeze, by giving the developer the choice between various front end solutions integrated into the Rust code.
So, what if we combine the best of both worlds?
That’s what I’m going to show in this post, by integrating Rust into a C++ codebase, I want to show how developers can harness the strengths of both languages to create superior software solutions.
Example Project: Conan/CMake Rust Integration
We’ll use a simple example project to demonstrate how to call C++ code from Rust. This project utilizes conan-rs and autocxx for seamless integration.
The full source code can be found in this repo.
Project Structure Overview
The project comprises two primary components:
- C++ Targets: Utilizes Conan for package management and CMake for build automation. There are two targets: A simple static lib, and a cli app that consumes it.
- Rust Component: Extends the codebase, taking advantage of Rust’s features for improved user experience using the Tauri Framework. It includes Rust bindings to C++ functions and a Tauri frontend.
wrapping up:
C++ Component
As I said, there is a simple static lib and a binary target:
The library code:
// header
#pragma once
namespace deep_thought {
int answer();
}
// impl
#include <deep_thought/answer.hpp>
namespace deep_thought {
int answer() {
return 42;
}
}
And the binary consumer:
#include <deep_thought/answer.hpp>
#include <chrono>
#include <iostream>
#include <thread>
auto main() -> int {
auto task = []() {
std::cout << "Thinking..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(10));
std::cout << "The answer is " << deep_thought::answer() << std::endl;
};
std::thread thread(task);
thread.join();
return 0;
}
After building these, we have the following:
Rust Integration
The Rust component demonstrates how to use Rust for user interface development while leveraging the existing C++ code. It includes:
- Dependency Management and Binding Generation: Using
conan-rs
andautocxx
for managing dependencies and generating Rust bindings. - Tauri Backend: Implementing the
deep_thought::answer
function in Rust, callable from the Tauri app. - Tauri Frontend: A GUI that interacts with the backend to display the computed answer.
The hard work is done in the build.rs file. Here we install the deps of the app (this example have none) and build the project using conan-rs. After, we generate all the rust bindings using autocxx and finaly link all together. I strongly recommend you to take a look at it.
Building a Combined Application
After this, we can call the c++ functions in our tauri code:
use autocxx::prelude::*;
include_cpp! {
#include "deep_thought/answer.hpp"
safety!(unsafe_ffi)
generate!("deep_thought::answer")
}
pub fn answer() -> i32 {
ffi::deep_thought::answer().into()
}
And finally, calling it from our yew frontend:
#[function_component(App)]
pub fn app() -> Html {
let answer_msg = use_state(|| String::new());
let answer_msg_rc = Rc::new(answer_msg.clone());
let answer = {
let answer_msg_rc = answer_msg_rc.clone();
Callback::from(move |e: MouseEvent| {
e.prevent_default();
let answer_msg = answer_msg_rc.clone();
spawn_local(async move {
let new_msg = invoke("answer", JsValue::UNDEFINED)
.await
.as_string()
.unwrap();
answer_msg.set(new_msg);
});
})
};
html! {
<main class="container">
<div class="row">
<img src="public/deep_thought.png" class="logo tauri" alt="deep_thought"/>
</div>
<form class="row" onclick={answer}>
<button type="button">{"Give me the answer to life, universe and everything"}</button>
</form>
<p><b>{ &*answer_msg }</b></p>
</main>
}
}
As a result we have nice desktop app using both tauri and our existing c++ code:
Conclusion: Complementing Strengths
This example illustrates how Rust and C++ can complement each other in a single project. While we take advantage of an existing C++ codebase , Rust contributes to a more robust and modern user interface. It’s not about which language is better, but how each can be used to its strengths to create better software.