Minimal application mixing C++ and Rust
This example uses cxx to generate bindings between C++ and Rust, and integrates the two parts through CMake.
It is basically an inverted version of cxx's demo code, using C++ for the entry point and a MultiBuf class, while implementing a simple blobstore-library in Rust.
How it works
In lib.rs we add bridge declarations for our Rust types:
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
// Rust types and signatures exposed to C++.
extern "Rust" {
type BlobstoreClient;
fn new_blobstore_client() -> Box<BlobstoreClient>;
fn put(&mut self, parts: Pin<&mut MultiBuf>) -> u64;
...
}
}
fn new_blobstore_client() -> Box<BlobstoreClient> {
Box::new(BlobstoreClient { blobs: HashMap::new() })
}
struct BlobstoreClient {
blobs: HashMap<u64, Blob>,
}
impl BlobstoreClient {
fn put(&mut self, mut parts: Pin<&mut MultiBuf>) -> u64 {
...
}
}
In build.rs we add logic to generate C++ bridging code from the declarations:
fn main() {
cxx_build::bridge("src/lib.rs");
println!("cargo:rerun-if-changed=src/lib.rs");
}
In CMakeLists.txt we add a custom command to trigger the Rust build:
add_custom_command(
OUTPUT ${BLOBSTORE_BRIDGE_CPP} ${BLOBSTORE_LIB}
COMMAND cargo build --manifest-path ${BLOBSTORE_CARGO_MANIFEST}
...
)
In main.cpp we include the generated C++ header, construct Rust types, and call their methods:
#include "lib.rs.h"
int main() {
auto client = org::blobstore::new_blobstore_client();
...
const auto blobid = client->put(buf);
}
The application also consumes C++ types from Rust (MultiBuf
), and leverages shared types between the two languages (BlobMetadata
).
To learn more about the bridging layer, check out cxx's documentation.
Building and running the code
git clone git@github.com:paandahl/cpp-with-rust.git
mkdir cpp-with-rust/build
cd cpp-with-rust/build
cmake ..
cmake --build .
./cpp_with_rust
NOTE: If you are using Windows, run these commands in the Developer PowerShell for VS.
Technical notes
- As opposed to the original cxx demo,
build.rs
only generates the C++ bridging code, without compiling it. Instead, we pass it in to the CMake build by referencing it inadd_executable()
. - For simplicity, this example always builds the Rust code in debug mode. See here for suggested changes to adhere to the specified
CMAKE_BUILD_TYPE
, and moving the cargo output to within the CMake build tree.
License
The code is available under the MIT license.