├── examples ├── basic_integration_example │ ├── rusty_lib │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Cargo.lock │ │ └── src │ │ │ └── lib.rs │ ├── cpp_user │ │ ├── Makefile │ │ └── src │ │ │ └── main.cpp │ └── README.md ├── concurrency_example │ ├── cpp_user_concurrent │ │ ├── lib │ │ │ └── librusty_lib_concurrent.so │ │ ├── Makefile │ │ └── src │ │ │ └── main.cpp │ ├── rusty_lib_concurrent │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── README.md └── producer_consumer_example │ ├── rusty_lib_producer_consumer │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── cpp_user_producer_consumer │ ├── lib │ │ └── librusty_lib_concurrent.so │ ├── Makefile │ └── src │ │ └── main.cpp │ └── README.md ├── .gitattributes ├── LICENSE ├── Dockerfile ├── scripts └── launcher.sh └── README.md /examples/basic_integration_example/rusty_lib/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /examples/**/*.rs linguist-language=Rust 2 | /examples/**/*.cpp linguist-language=C++ 3 | /examples/**/*.h linguist-language=C++ -------------------------------------------------------------------------------- /examples/basic_integration_example/rusty_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty_lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | 8 | [lib] 9 | crate-type = ["cdylib"] -------------------------------------------------------------------------------- /examples/concurrency_example/cpp_user_concurrent/lib/librusty_lib_concurrent.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeryusufyagci/rust-cpp-integration/HEAD/examples/concurrency_example/cpp_user_concurrent/lib/librusty_lib_concurrent.so -------------------------------------------------------------------------------- /examples/producer_consumer_example/rusty_lib_producer_consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty_lib_producer_consumer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | 8 | [lib] 9 | crate-type = ["cdylib"] -------------------------------------------------------------------------------- /examples/basic_integration_example/rusty_lib/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "rusty_lib" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/concurrency_example/rusty_lib_concurrent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty_lib_concurrent" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rand = "0.8.5" 8 | 9 | [lib] 10 | crate-type = ["cdylib"] -------------------------------------------------------------------------------- /examples/producer_consumer_example/cpp_user_producer_consumer/lib/librusty_lib_concurrent.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeryusufyagci/rust-cpp-integration/HEAD/examples/producer_consumer_example/cpp_user_producer_consumer/lib/librusty_lib_concurrent.so -------------------------------------------------------------------------------- /examples/basic_integration_example/cpp_user/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CXXFLAGS = -std=c++14 -Wall -Wextra 3 | 4 | SRCDIR = src 5 | OBJDIR = obj 6 | BINDIR = bin 7 | LIBDIR = lib 8 | 9 | # Target executable 10 | TARGET = $(BINDIR)/greeter_example 11 | 12 | # Rust lib location 13 | RUST_LIB_DIR = $(LIBDIR) 14 | RUST_LIB = rusty_lib 15 | 16 | # Sources 17 | SRCS = $(wildcard $(SRCDIR)/*.cpp) 18 | OBJS = $(SRCS:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o) 19 | 20 | # Ensure dir location 21 | $(shell mkdir -p $(OBJDIR) $(BINDIR)) 22 | 23 | # Linking 24 | $(TARGET): $(OBJS) 25 | $(CXX) $(CXXFLAGS) $(OBJS) -L$(RUST_LIB_DIR) -l$(RUST_LIB) -o $@ 26 | 27 | # Compiling 28 | $(OBJDIR)/%.o: $(SRCDIR)/%.cpp 29 | $(CXX) $(CXXFLAGS) -c $< -o $@ 30 | 31 | # Clean up build artifacts 32 | clean: 33 | rm -rf $(OBJS) $(BINDIR)/* 34 | 35 | .PHONY: clean 36 | -------------------------------------------------------------------------------- /examples/concurrency_example/cpp_user_concurrent/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CXXFLAGS = -std=c++14 -Wall -Wextra -pthread 3 | 4 | SRCDIR = src 5 | OBJDIR = obj 6 | BINDIR = bin 7 | LIBDIR = lib 8 | 9 | # Target executable 10 | TARGET = $(BINDIR)/concurrency_example 11 | 12 | # Rust lib location 13 | RUST_LIB_DIR = $(LIBDIR) 14 | RUST_LIB = rusty_lib_concurrent 15 | 16 | # Sources 17 | SRCS = $(wildcard $(SRCDIR)/*.cpp) 18 | OBJS = $(SRCS:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o) 19 | 20 | # Ensure dir location 21 | $(shell mkdir -p $(OBJDIR) $(BINDIR)) 22 | 23 | # Linking 24 | $(TARGET): $(OBJS) 25 | $(CXX) $(CXXFLAGS) $(OBJS) -L$(RUST_LIB_DIR) -l$(RUST_LIB) -o $@ 26 | 27 | # Compiling 28 | $(OBJDIR)/%.o: $(SRCDIR)/%.cpp 29 | $(CXX) $(CXXFLAGS) -c $< -o $@ 30 | 31 | # Clean up build artifacts 32 | clean: 33 | rm -rf $(OBJS) $(BINDIR)/* 34 | 35 | .PHONY: clean 36 | -------------------------------------------------------------------------------- /examples/producer_consumer_example/cpp_user_producer_consumer/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CXXFLAGS = -std=c++14 -Wall -Wextra 3 | 4 | SRCDIR = src 5 | OBJDIR = obj 6 | BINDIR = bin 7 | LIBDIR = lib 8 | 9 | # Target executable 10 | TARGET = $(BINDIR)/producer_consumer_example 11 | 12 | # Rust lib location 13 | RUST_LIB_DIR = $(LIBDIR) 14 | RUST_LIB = rusty_lib_producer_consumer 15 | 16 | # Sources 17 | SRCS = $(wildcard $(SRCDIR)/*.cpp) 18 | OBJS = $(SRCS:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o) 19 | 20 | # Ensure dir location 21 | $(shell mkdir -p $(OBJDIR) $(BINDIR)) 22 | 23 | # Linking 24 | $(TARGET): $(OBJS) 25 | $(CXX) $(CXXFLAGS) $(OBJS) -L$(RUST_LIB_DIR) -l$(RUST_LIB) -o $@ 26 | 27 | # Compiling 28 | $(OBJDIR)/%.o: $(SRCDIR)/%.cpp 29 | $(CXX) $(CXXFLAGS) -c $< -o $@ 30 | 31 | # Clean up build artifacts 32 | clean: 33 | rm -rf $(OBJS) $(BINDIR)/* 34 | 35 | .PHONY: clean 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /examples/basic_integration_example/rusty_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Greeter interface 2 | pub trait Greeter { 3 | fn say_hello(&self); 4 | } 5 | 6 | // Struct that will provide Greeter implementations 7 | pub struct RustGreeter { 8 | name: String, 9 | } 10 | 11 | impl Greeter for RustGreeter { 12 | fn say_hello(&self) { 13 | println!("Hello from RustGreeter! My name is {}", self.name); 14 | } 15 | } 16 | 17 | #[no_mangle] 18 | pub extern "C" fn generate_greeter(name: *const std::os::raw::c_char) -> *mut RustGreeter { 19 | if name.is_null() { 20 | return std::ptr::null_mut(); 21 | } 22 | 23 | let c_str = unsafe { std::ffi::CStr::from_ptr(name) }; 24 | let rust_string = match c_str.to_str() { 25 | Ok(s) => s.to_owned(), 26 | Err(_) => return std::ptr::null_mut(), 27 | }; 28 | 29 | let greeter = RustGreeter { name: rust_string }; 30 | Box::into_raw(Box::new(greeter)) 31 | } 32 | 33 | #[no_mangle] 34 | pub extern "C" fn greeter_say_hello(greeter: *mut RustGreeter) { 35 | unsafe { 36 | if let Some(g) = greeter.as_ref() { 37 | g.say_hello(); 38 | } 39 | } 40 | } 41 | 42 | #[no_mangle] 43 | pub extern "C" fn destroy_greeter(greeter: *mut RustGreeter) { 44 | if !greeter.is_null() { 45 | unsafe { 46 | drop(Box::from_raw(greeter)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base stage: To reuse dependencies accross different stages 2 | FROM ubuntu:22.04 AS base 3 | 4 | # Install common dependencies to both Rust and C++ 5 | RUN apt-get update && apt-get install -y \ 6 | gcc \ 7 | g++ \ 8 | make \ 9 | curl \ 10 | git \ 11 | && apt-get clean 12 | 13 | # Keeping it in base for now, although only required for Rust stage 14 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 15 | ENV PATH="/root/.cargo/bin:${PATH}" 16 | 17 | # Stage 1: Build Rust library 18 | FROM base AS rust_builder 19 | 20 | ARG RUST_LIB_DIR 21 | WORKDIR /usr/src/rust_lib 22 | COPY ${RUST_LIB_DIR}/ . 23 | RUN cargo build --release --target=x86_64-unknown-linux-gnu 24 | 25 | # Stage 2: Build the C++ user using the Rust library 26 | FROM base AS cpp_builder 27 | 28 | ARG CPP_PROJECT_DIR 29 | WORKDIR /usr/src/cpp_project 30 | COPY ${CPP_PROJECT_DIR}/ . 31 | 32 | # Copy the Rust .so from Stage 1 33 | COPY --from=rust_builder /usr/src/rust_lib/target/x86_64-unknown-linux-gnu/release/*.so ./lib/ 34 | 35 | # Make the library visible 36 | ENV LD_LIBRARY_PATH=/usr/src/cpp_project/lib:$LD_LIBRARY_PATH 37 | RUN make 38 | 39 | # Ensure permissions 40 | RUN chmod +x ./bin/${BINARY_NAME} 41 | 42 | # Run the binary 43 | # TODO: had to use shell form, problems on some machines otherwise, to be checked 44 | ARG BINARY_NAME 45 | ENV BINARY_NAME=${BINARY_NAME} 46 | CMD sh -c "./bin/${BINARY_NAME}" 47 | -------------------------------------------------------------------------------- /examples/producer_consumer_example/cpp_user_producer_consumer/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Extern the Rust API with C linkage 6 | extern "C" { 7 | struct SharedQueue; 8 | 9 | SharedQueue* make_shared_queue(); 10 | void produce_task(SharedQueue* queue, int value); 11 | int consume_task(SharedQueue* queue); 12 | void start_producer(SharedQueue* queue); 13 | } 14 | 15 | class Consumer { 16 | public: 17 | Consumer() { 18 | queue = make_shared_queue(); 19 | } 20 | 21 | void consumeTask() { 22 | int value = consume_task(queue); 23 | std::cout << "C++ finished TASK-" << value << std::endl; 24 | } 25 | 26 | void startConsuming() { 27 | int nb_tasks = 10; 28 | 29 | std::thread consumer_thread([this, nb_tasks]() { 30 | for (int i = 0; i < nb_tasks; ++i) { 31 | consumeTask(); 32 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Introduce some delay to simulate work on a task 33 | } 34 | }); 35 | consumer_thread.join(); // block main thread until consumer_thread finishes 36 | } 37 | 38 | void startProducer() { 39 | // Start the Rust producer 40 | start_producer(queue); 41 | } 42 | 43 | private: 44 | SharedQueue* queue; 45 | }; 46 | 47 | int main() { 48 | Consumer consumer; 49 | 50 | consumer.startProducer(); 51 | consumer.startConsuming(); 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /examples/concurrency_example/cpp_user_concurrent/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Extern the Rust API with C linkage 5 | extern "C" { 6 | struct TemperatureSensor; 7 | 8 | TemperatureSensor* generate_sensor(); 9 | void start_sensor(TemperatureSensor* sensor, uint64_t seed); 10 | float* get_readings(TemperatureSensor* sensor); 11 | size_t get_readings_len(TemperatureSensor* sensor); 12 | void free_readings(float* readings); 13 | void wait_for_data(TemperatureSensor* sensor); 14 | } 15 | 16 | float calculate_running_average(float* readings, size_t len) { 17 | float sum = 0.0; 18 | for (size_t i = 0; i < len; ++i) { 19 | sum += readings[i]; 20 | } 21 | return sum / len; 22 | } 23 | 24 | void process_temperatures(TemperatureSensor* sensor) { 25 | for (int i = 0; i < 10; ++i) { 26 | wait_for_data(sensor); // Data ready will be signalled via condition variable from Rust 27 | 28 | size_t len = get_readings_len(sensor); 29 | if (len > 0) { 30 | float* readings = get_readings(sensor); 31 | float average = calculate_running_average(readings, len); 32 | std::cout << "C++: Running average temperature: " << average << std::endl; 33 | 34 | free_readings(readings); // Rust will free the memory 35 | } 36 | } 37 | } 38 | 39 | int main() { 40 | TemperatureSensor* sensor = generate_sensor(); 41 | 42 | // Start the sensor with a seed 43 | start_sensor(sensor, 1234); 44 | 45 | process_temperatures(sensor); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /scripts/launcher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Select an example to build and run:" 6 | echo "1) Basic Integration Example" 7 | echo "2) Basic Concurrency Example" 8 | echo "3) Producer-Consumer Example" 9 | read -p "Enter choice [1-3]: " choice 10 | 11 | case $choice in 12 | # TODO: provide a utility that will auto-generate the variables from example dir name. 13 | # Probably will require following naming conventions for examples 14 | 15 | 1) 16 | RUST_LIB_DIR="examples/basic_integration_example/rusty_lib" 17 | CPP_PROJECT_DIR="examples/basic_integration_example/cpp_user" 18 | BINARY_NAME="greeter_example" 19 | ;; 20 | 2) 21 | RUST_LIB_DIR="examples/concurrency_example/rusty_lib_concurrent" 22 | CPP_PROJECT_DIR="examples/concurrency_example/cpp_user_concurrent" 23 | BINARY_NAME="concurrency_example" 24 | ;; 25 | 3) 26 | RUST_LIB_DIR="examples/producer_consumer_example/rusty_lib_producer_consumer" 27 | CPP_PROJECT_DIR="examples/producer_consumer_example/cpp_user_producer_consumer" 28 | BINARY_NAME="producer_consumer_example" 29 | ;; 30 | *) 31 | echo "Invalid choice." 32 | exit 1 33 | ;; 34 | esac 35 | 36 | echo "Building and running ${CPP_PROJECT_DIR} with Rust library ${RUST_LIB_DIR} and binary ${BINARY_NAME}..." 37 | 38 | docker build \ 39 | --build-arg RUST_LIB_DIR=${RUST_LIB_DIR} \ 40 | --build-arg CPP_PROJECT_DIR=${CPP_PROJECT_DIR} \ 41 | --build-arg BINARY_NAME=${BINARY_NAME} \ 42 | -t rust_cpp_example . 43 | 44 | docker run --rm rust_cpp_example 45 | -------------------------------------------------------------------------------- /examples/basic_integration_example/cpp_user/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Forward declare the Rust struct 6 | struct RustGreeter; 7 | 8 | extern "C" { 9 | RustGreeter* generate_greeter(const char* name); 10 | void greeter_say_hello(RustGreeter* greeter); 11 | void destroy_greeter(RustGreeter* greeter); 12 | } 13 | 14 | // C++ base class acting as a wrapper for the Rust struct 15 | class Greeter { 16 | public: 17 | explicit Greeter(const std::string& name) { 18 | greeter = generate_greeter(name.c_str()); 19 | if (!greeter) { 20 | throw std::runtime_error("Failed to generate Greeter"); 21 | } 22 | } 23 | 24 | virtual ~Greeter() { 25 | if (greeter) { 26 | destroy_greeter(greeter); 27 | } 28 | } 29 | 30 | virtual void sayHello() const { 31 | if (greeter) { 32 | greeter_say_hello(greeter); 33 | } 34 | } 35 | 36 | protected: 37 | RustGreeter* greeter; 38 | }; 39 | 40 | // C++ derived class 41 | class CustomGreeter : public Greeter { 42 | public: 43 | explicit CustomGreeter(const std::string& name) : Greeter(name) {} 44 | 45 | void sayHello() const override { 46 | Greeter::sayHello(); 47 | std::cout << "CustomGreeter says: Hello from C++, extending a Rust Struct!" << std::endl; // need to flush to ensure order of output 48 | } 49 | 50 | }; 51 | 52 | int main() { 53 | // Stack allocation 54 | CustomGreeter stackGreeter("Stack Allocated C++ User"); 55 | stackGreeter.sayHello(); 56 | 57 | // Heap allocation with raw pointers 58 | CustomGreeter* heapGreeter = new CustomGreeter("Heap Allocated C++ User"); 59 | heapGreeter->sayHello(); 60 | delete heapGreeter; 61 | 62 | // Heap allocation with smart pointers 63 | std::unique_ptr smartGreeter = std::make_unique("Smart Pointer C++ User"); 64 | smartGreeter->sayHello(); 65 | 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /examples/basic_integration_example/README.md: -------------------------------------------------------------------------------- 1 | # Basic Integration Example 2 | 3 | This example demonstrates the fundamental steps required to integrate a Rust library into an existing C++ codebase as a shared object. 4 | 5 | It serves as an introductory guide to Rust-C++ interoperability, showcasing how Rust can be used within a legacy C++ project. 6 | 7 | ## Key Concepts 8 | * **Basic FFI Integration**: Demonstrates how to expose Rust code to C++ using the Foreign Function Interface (FFI). 9 | * **Cross-Language Integration**: Illustrates how to wrap Rust structs and extend them in C++, allowing native extension of Rust APIs within C++. 10 | * **Architecture-Targeted Compilation**: Demonstrates cross-compilation by building the Rust library (`.so` file) with an architecture-targeted flag, such as `x86_64-unknown-linux-gnu`. 11 | * **Note**: Previously, this example included cross-distribution builds (e.g., building on one Linux distribution and using the library on another). With the streamlined Dockerfiles, this specific use case has been removed. If there’s interest, it can be reintroduced as a dedicated example. 12 | 13 | ## How to run 14 | 15 | From project root, run: 16 | 17 | ```bash 18 | scripts/launcher.sh # Select `Basic Integration Example` 19 | ``` 20 | 21 | ### Expected Output 22 | 23 | You should see the following output: 24 | 25 | ``` 26 | CustomGreeter says: Hello from C++, extending a Rust Struct! 27 | Hello from RustGreeter! My name is Stack Allocated C++ User 28 | CustomGreeter says: Hello from C++, extending a Rust Struct! 29 | Hello from RustGreeter! My name is Heap Allocated C++ User 30 | CustomGreeter says: Hello from C++, extending a Rust Struct! 31 | Hello from RustGreeter! My name is Smart Pointer C++ User 32 | ``` 33 | 34 | Explanation 35 | * **Rust Code**: Implements a RustGreeter struct and a Greeter trait, exposing functions to C++ via FFI. This allows C++ to interact with Rust as if it were a native library. 36 | * **C++ Code**: Demonstrates how to utilize the Rust library by integrating it into a C++ project. -------------------------------------------------------------------------------- /examples/producer_consumer_example/rusty_lib_producer_consumer/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::sync::{Arc, Condvar, Mutex}; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | // A thread-safe queue with a condition variable to signal when an item is available 7 | pub struct SharedQueue { 8 | queue: Mutex>, 9 | condvar: Condvar, 10 | } 11 | 12 | impl SharedQueue { 13 | pub fn new() -> Self { 14 | SharedQueue { 15 | queue: Mutex::new(VecDeque::new()), 16 | condvar: Condvar::new(), 17 | } 18 | } 19 | 20 | pub fn produce(&self, value: i32) { 21 | let mut queue = self.queue.lock().unwrap(); 22 | queue.push_back(value); 23 | println!("Rust produced: TASK-{}", value); 24 | self.condvar.notify_one(); // data-ready for C++ 25 | } 26 | 27 | pub fn consume(&self) -> i32 { 28 | // Safely dequeue an item 29 | 30 | let mut queue = self.queue.lock().unwrap(); 31 | while queue.is_empty() { 32 | queue = self.condvar.wait(queue).unwrap(); 33 | } 34 | let value = queue.pop_front().unwrap(); 35 | value 36 | } 37 | } 38 | 39 | #[no_mangle] 40 | pub extern "C" fn make_shared_queue() -> *mut SharedQueue { 41 | // Expose a heap allocated SharedQueue as a raw ptr 42 | Box::into_raw(Box::new(SharedQueue::new())) 43 | } 44 | 45 | #[no_mangle] 46 | pub extern "C" fn produce_task(queue: *mut SharedQueue, value: i32) { 47 | // Unsafely take the queue handle from the C++ side, and queue an item 48 | let queue = unsafe { &*queue }; 49 | queue.produce(value); 50 | } 51 | 52 | #[no_mangle] 53 | pub extern "C" fn consume_task(queue: *mut SharedQueue) -> i32 { 54 | // Unsafely take the queue handle from the C++ side, and dequeue an item 55 | let queue = unsafe { &*queue }; 56 | queue.consume() 57 | } 58 | 59 | #[no_mangle] 60 | pub extern "C" fn start_producer(queue: *mut SharedQueue) { 61 | let queue = unsafe { Arc::from_raw(queue) }; 62 | let nb_iterations = 10; 63 | 64 | thread::spawn(move || { 65 | for i in 1..=nb_iterations { 66 | queue.produce(i); 67 | thread::sleep(Duration::from_millis(250)); // Introduce some delay required to produce tasks 68 | } 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /examples/producer_consumer_example/README.md: -------------------------------------------------------------------------------- 1 | # Producer-Consumer Example 2 | 3 | This example demonstrates a typical implementation of the producer-consumer pattern, showcasing concurrency-safe interoperability between Rust and C++. 4 | 5 | In this example, Rust acts as the producer, generating a sequence of tasks and placing them in a thread-safe queue. C++ acts as the consumer, dequeuing these tasks by finishing them. 6 | 7 | This example is meant to provide a more advanced look at how to manage shared resources between Rust and C++ in a multi-threaded environment, highlighting the use of synchronization mechanisms to ensure safe and efficient concurrency. 8 | 9 | ## Key Concepts 10 | * Producer-Consumer Pattern with Synchronization: Demonstrates how to implement the classic producer-consumer pattern using a shared, thread-safe queue protected by a mutex and synchronized with a condition variable. 11 | * Cross-Language Integration: Shows how to share data, manage memory, and synchronize operations between Rust and C++ in a concurrent setting. 12 | 13 | # How to Run 14 | 15 | From the project root, run: 16 | 17 | ```bash 18 | scripts/launcher.sh # Select `Producer-Consumer Example` 19 | ``` 20 | 21 | ## Expected Output 22 | 23 | You should see output similar to the following, where Rust produces some tasks and C++ consumes them: 24 | 25 | ``` 26 | Rust produced: TASK-1 27 | C++ finished TASK-1 28 | Rust produced: TASK-2 29 | C++ finished TASK-2 30 | Rust produced: TASK-3 31 | C++ finished TASK-3 32 | Rust produced: TASK-4 33 | C++ finished TASK-4 34 | Rust produced: TASK-5 35 | C++ finished TASK-5 36 | Rust produced: TASK-6 37 | C++ finished TASK-6 38 | Rust produced: TASK-7 39 | C++ finished TASK-7 40 | Rust produced: TASK-8 41 | C++ finished TASK-8 42 | Rust produced: TASK-9 43 | C++ finished TASK-9 44 | Rust produced: TASK-10 45 | C++ finished TASK-10 46 | ``` 47 | 48 | ## Explanation 49 | * **Rust Code**: Rust acts as the producer, generating a predefined number of tasks and placing them into a thread-safe queue. The queue is protected by a mutex to ensure access safety. A condition variable is used to notify the C++ consumer when new tasks are available. This setup ensures that Rust can safely and efficiently produce tasks without interfering with the C++ side. 50 | * **C++ Code**: C++ acts as the consumer, retrieving tasks from the queue and processing them. The consumer waits for tasks to be available, processes each task, and then waits for the next one. The condition variable used by Rust ensures that C++ is notified as soon as a new task is ready, allowing for efficient task processing without busy-waiting. 51 | * **Producer-Consumer Pattern**: This example demonstrates a typical producer-consumer pattern in a multi-threaded, cross-language context. Rust and C++ work together to manage shared resources, with Rust producing tasks and C++ consuming them. The use of mutexes and condition variables ensures that the two languages operate efficiently and safely in a concurrent environment. -------------------------------------------------------------------------------- /examples/concurrency_example/README.md: -------------------------------------------------------------------------------- 1 | # Basic Concurrency Example 2 | 3 | This example provides an introductory look at concurrency-safe interoperability between Rust and C++ using a simple batch processing approach. 4 | 5 | On the Rust side, mock temperature sensor data is generated, which is then batch-processed in C++, where a running average is calculated and displayed. 6 | 7 | While this example serves as an entry point for experimenting with concurrency-safe interop between the two languages, for more advanced use cases, please see the `producer_consumer_example`, which more closely follows the producer-consumer pattern. 8 | 9 | ## Key Concepts 10 | * Batch Processing with Synchronization: Demonstrates basic cross-language synchronization to ensure that data is fully generated in Rust before being processed in C++ 11 | * Introduction to Cross-Language Integration: The example illustrates how to share data, manage memory, and synchronize operations between Rust and C++. 12 | 13 | ## How to run 14 | 15 | From project root, run: 16 | 17 | ```bash 18 | scripts/launcher.sh # Select `Basic Concurrency Example` 19 | ``` 20 | 21 | ### Expected Output 22 | 23 | The mock temperature data is seeded, so you should see the following output: 24 | 25 | ``` 26 | Rust: Generated temperature: 20.28 27 | C++: Running average temperature: 20.2829 28 | Rust: Generated temperature: 20.59 29 | C++: Running average temperature: 20.4376 30 | Rust: Generated temperature: 24.98 31 | C++: Running average temperature: 21.952 32 | Rust: Generated temperature: 20.85 33 | C++: Running average temperature: 21.676 34 | Rust: Generated temperature: 21.57 35 | C++: Running average temperature: 21.6558 36 | Rust: Generated temperature: 23.08 37 | C++: Running average temperature: 21.8927 38 | Rust: Generated temperature: 23.52 39 | C++: Running average temperature: 22.125 40 | Rust: Generated temperature: 21.90 41 | C++: Running average temperature: 22.0966 42 | Rust: Generated temperature: 23.45 43 | C++: Running average temperature: 22.2475 44 | Rust: Generated temperature: 23.22 45 | C++: Running average temperature: 22.3448 46 | ``` 47 | 48 | ## Explanation 49 | * **Rust Code**: In this example, Rust is responsible for generating mock temperature data in a separate thread, simulating sensor readings. The data is stored in a thread-safe buffer, which is protected by a mutex to ensure safe access from multiple threads. Rust uses a condition variable to notify C++ when new data is available, ensuring that the C++ side only processes fully generated data. 50 | * **C++ Code**: C++ processes the mock temperature data produced by Rust, calculating a running average. The synchronization between Rust and C++ ensures that data is fully generated before being processed, preventing race conditions and ensuring data consistency. 51 | * **Synchronization Mechanisms**: This example uses mutexes and condition variables to manage concurrency. Rust produces the data and notifies C++ when the data is ready for processing. C++ waits for this signal, processes the data, and then waits for the next signal, ensuring that both languages operate in sync. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust and C++ Integration Examples 2 | This project provides a curated collection of examples that demonstrate how to integrate a Rust library into an existing C++ codebase as a shared object. From basic integration techniques to more advanced patterns, these examples aim to offer a comprehensive catalog of Rust-C++ interoperability scenarios. 3 | 4 | The project was inspired by real-world challenges I encountered while integrating a new Rust library into a legacy C++ codebase. I hope these examples will help you understand the key mechanisms behind successful Rust-C++ integration. 5 | 6 | Contributions are welcome! I encourage you to contribute your own examples and help build a robust catalog of solutions for real-world challenges that arise in Rust-C++ integration. 7 | 8 | ## Project Structure 9 | 10 | * **examples/**: Contains various example projects that demonstrate different aspects of Rust and C++ integration. Each sub-directory includes a README with documentation. 11 | * **basic_integration/**: A simple example demonstrating basic Rust-C++ integration. 12 | * **concurrency_example/**: A batch processing example showcasing concurrency-safe interoperability between Rust and C++. 13 | * **producer_consumer_example/**: An advanced example implementing the producer-consumer pattern using Rust and C++. 14 | * **scripts/**: Contains a helper script (`launcher.sh`) for building and running the examples. 15 | 16 | ## Docker Setup 17 | 18 | The project has been consolidated to use a single Docker setup, streamlining the build process for both Rust and C++ code. This approach simplifies the environment and ensures consistency across different examples. 19 | 20 | ### Key Features: 21 | * **Unified Dockerfile**: Instead of separate Dockerfiles per example and platform, a single Dockerfile now handles the building of Rust libraries and the C++ code. 22 | * **Multi-Stage Build**: The Dockerfile is optimized to reuse common dependencies and efficiently compile both the libraries and the user code, reducing the final image size and speeding up the build process. 23 | * **Parametric Builds**: The Dockerfile is designed to be flexible, using build arguments to handle different examples and binary names. This makes it easy to add new examples or modify existing ones without needing to change the Dockerfile itself. 24 | 25 | ## Getting Started 26 | 27 | The repository includes a launcher script that builds and runs the Docker containers for you. 28 | 29 | ### Dependencies 30 | 31 | Since all examples are containerized, docker is the only dependency. Please make sure it's installed. 32 | 33 | For Fedora or RHEL-based systems: 34 | ```bash 35 | sudo dnf install docker 36 | ``` 37 | 38 | **Start the launcher**: 39 | ```bash 40 | scripts/launcher.sh 41 | ``` 42 | 43 | **Select an Example**: The launcher will prompt you to select an example from the list below: 44 | * Basic Integration Example 45 | * Concurrency Example 46 | * Producer-Consumer Example 47 | 48 | **Enjoy Your Sandbox!**: The launcher will build and run your selected example. 49 | * The first run will take longer as dependencies are resolved. Subsequent runs will be significantly faster. 50 | * Monitor the output to understand how it works; modify the code and re-run to quickly prototype your custom implementations! 51 | 52 | ## Contributing 53 | 54 | Contributions to this project are highly encouraged and greatly appreciated. If you have an example that showcases Rust-C++ interoperability or ideas for improving existing examples, please share them. 55 | 56 | **How to Contribute**: 57 | 1. Fork this repository. 58 | 2. Open a new branch for your changes. 59 | 3. Include a README.md (that follows the existing documentation conventions) in your example root. 60 | 4. Test your changes to ensure they work. 61 | 5. Submit a pull request. 62 | 63 | ## LICENSE 64 | 65 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /examples/concurrency_example/rusty_lib_concurrent/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rand::rngs::StdRng; 2 | use rand::Rng; 3 | use rand::SeedableRng; 4 | use std::sync::{Arc, Condvar, Mutex}; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | pub struct TemperatureSensor { 9 | buffer: Arc<(Mutex>, Condvar)>, // Thread-safe buffer 10 | new_data: Arc<(Mutex, Condvar)>, // Flag and condition variable to notify when new data is available 11 | } 12 | 13 | impl TemperatureSensor { 14 | pub fn new() -> Self { 15 | TemperatureSensor { 16 | buffer: Arc::new((Mutex::new(Vec::new()), Condvar::new())), 17 | new_data: Arc::new((Mutex::new(false), Condvar::new())), 18 | } 19 | } 20 | 21 | pub fn start(&self, seed: u64) { 22 | let buffer = Arc::clone(&self.buffer); 23 | let new_data = Arc::clone(&self.new_data); 24 | let nb_iterations = 10; 25 | 26 | thread::spawn(move || { 27 | let mut rng = StdRng::seed_from_u64(seed); // Seeded RNG; use `rand::thread_rng();` for random data 28 | for _ in 0..nb_iterations { 29 | let temp = rng.gen_range(20.0..25.0); 30 | 31 | // Safely update the buffer and notify cpp-side 32 | { 33 | let (lock, cvar) = &*buffer; 34 | let mut buf = lock.lock().unwrap(); 35 | buf.push(temp); 36 | println!("Rust: Generated temperature: {:.2}", temp); 37 | cvar.notify_one(); // data-ready 38 | } 39 | 40 | // Safely update the flag and notify cpp-side 41 | { 42 | let (lock, cvar) = &*new_data; 43 | let mut flag = lock.lock().unwrap(); 44 | *flag = true; 45 | cvar.notify_one(); // data-ready 46 | } 47 | 48 | // Introduce some sensor delay 49 | thread::sleep(Duration::from_millis(500)); 50 | } 51 | }); 52 | } 53 | 54 | pub fn get_readings(&self) -> Vec { 55 | // Safely access the buffer 56 | 57 | let (lock, _) = &*self.buffer; 58 | let buffer = lock.lock().unwrap(); 59 | buffer.clone() 60 | } 61 | 62 | pub fn wait_for_data(&self) { 63 | // Wait for new sensor data from Rust-side 64 | 65 | let (lock, cvar) = &*self.new_data; 66 | let mut flag = lock.lock().unwrap(); 67 | 68 | while !*flag { 69 | flag = cvar.wait(flag).unwrap(); 70 | } 71 | *flag = false; 72 | } 73 | } 74 | 75 | #[no_mangle] 76 | pub extern "C" fn generate_sensor() -> *mut TemperatureSensor { 77 | Box::into_raw(Box::new(TemperatureSensor::new())) 78 | } 79 | 80 | #[no_mangle] 81 | pub extern "C" fn start_sensor(sensor: *mut TemperatureSensor, seed: u64) { 82 | let sensor = unsafe { &*sensor }; 83 | sensor.start(seed); 84 | } 85 | 86 | #[no_mangle] 87 | pub extern "C" fn get_readings(sensor: *mut TemperatureSensor) -> *mut f32 { 88 | let sensor = unsafe { &*sensor }; 89 | let readings = sensor.get_readings(); 90 | let mut vec = readings.into_boxed_slice(); 91 | let ptr = vec.as_mut_ptr(); 92 | std::mem::forget(vec); // no-dealloc by Rust; let C++ handle the heap allocation (C++ will ask Rust to free when it's done, see: `free_readings()`) 93 | ptr 94 | } 95 | 96 | #[no_mangle] 97 | pub extern "C" fn get_readings_len(sensor: *mut TemperatureSensor) -> usize { 98 | let sensor = unsafe { &*sensor }; 99 | sensor.get_readings().len() 100 | } 101 | 102 | #[no_mangle] 103 | pub extern "C" fn wait_for_data(sensor: *mut TemperatureSensor) { 104 | let sensor = unsafe { &*sensor }; 105 | sensor.wait_for_data(); 106 | } 107 | 108 | #[no_mangle] 109 | pub extern "C" fn free_readings(readings: *mut f32) { 110 | unsafe { 111 | drop(Box::from_raw(readings)); // Reclaim the memory allocated by C++ for the buffer 112 | } 113 | } 114 | --------------------------------------------------------------------------------