Basic Rust and C++ InterOp

Table of contents

No heading

No headings in the article.

Interoperability between different programming languages is a common requirement in many software projects. In this article, we will explore how to interoperate between the Rust and C++ programming languages, allowing you to use the strengths of both languages in your projects.

Rust is a modern, statically-typed programming language that offers low-level control and concurrency without sacrificing safety and reliability. C++ is a high-performance, low-level programming language that is widely used in systems programming and other performance-critical applications.

To interop between Rust and C++, we will use the rust-cpython crate, which provides safe and idiomatic Rust bindings for the Python C API. This crate allows you to call Rust functions from C++ and vice versa, making it easy to integrate Rust and C++ code in your projects.

To begin, we will add the rust-cpython crate to our Cargo.toml file as a dependency.

[dependencies]
rust-cpython = "0.4.0"

Next, we will import the rust-cpython crate and the Python and PyObject types in our Rust code.

use rust_cpython::{Python, PyObject};

Now, we can define a Rust function that we want to call from C++. This function must take a &Python argument and return a PyObject. In this example, we will define a simple add function that takes two i32 arguments and returns their sum.

fn add(py: &Python, a: i32, b: i32) -> PyObject {
    // Return the sum of the two arguments.
    (a + b).to_py_object(py)
}

To call this function from C++, we will use the rust_cpython::PyModule class to define a Rust module that exports the add function. We will then use the PyImport_AppendInittab function from the Python C API to register the Rust module and make it available to C++.

rust_cpython::PyModule::new(py, "rust")
    .add("add", py_fn!(py, add(a: i32, b: i32)))
    .register()
    .unwrap();

// Register the Rust module in the Python interpreter.
PyImport_AppendInittab("rust", rust_cpython::init_module);

Now, we can call the add function from C++ using the Python C API. To do this, we will import the Python.h header, which provides declarations for the Python C API. We will then use the PyImport_ImportModule function to import the Rust module, the PyObject_GetAttrString function to get the add function from the module, and the PyObject_CallObject function to call the add function with the desired arguments.

#include <Python.h>

// Import the Rust module.
PyObject* module = PyImport_ImportModule("rust");

// Get the `add` function from the module.
PyObject* func = PyObject_GetAttrString(module, "add");

// Call the `add` function with the desired arguments.
PyObject* result = PyObject_CallObject(func, Py_BuildValue("(ii)", 1, 2));

// Print the result.
printf("%d\n", PyLong_AsLong(result));

This code will print 3 to the standard output, indicating that the add function was called successfully from C++.

In addition to calling Rust functions from C++, you can also call C++ functions from Rust using the rust-cpython crate. To do this, you will need to define the C++ function using the extern "C" keyword, which ensures that the function has a C-compatible signature.

For example, if you have a C++ function called multiply that takes two int arguments and returns their product, you can define the function in a header file as follows:

#ifndef MULTIPLY_H
#define MULTIPLY_H

#ifdef __cplusplus
extern "C" {
#endif

// Define the `multiply` function.
int multiply(int a, int b);

#ifdef __cplusplus
}
#endif

#endif

You can then implement the multiply function in a C++ source file and compile it as a shared library.

#include "multiply.h"

// Implement the `multiply` function.
int multiply(int a, int b) {
    return a * b;
}

Once you have compiled the shared library, you can call the multiply function from Rust using the rust-cpython::PyCapsule class. This class allows you to create a Rust wrapper for a C++ function, which you can then call from Rust using the call method.

// Import the `PyCapsule` type from the `rust-cpython` crate.
use rust_cpython::PyCapsule;

// Define a Rust wrapper for the `multiply` function.
struct Multiply {
    multiply: PyCapsule,
}

impl Multiply {
    fn new(py: &Python) -> Self {
        // Load the `multiply` shared library.
        let multiply = PyCapsule::new(py, "multiply", "multiply").unwrap();

        Self { multiply }
    }

    // Define a method that calls the `multiply` function.

To call the multiply function from Rust, you can define a method on the Multiply struct that uses the call method of the PyCapsule to call the C++ function. This method takes the arguments for the function as a PyTuple and returns the result as a PyObject.

impl Multiply {
    // ...

    // Define a method that calls the `multiply` function.
    fn multiply(&self, py: &Python, a: i32, b: i32) -> PyObject {
        // Call the `multiply` function with the desired arguments.
        self.multiply
            .call(py, (a, b).to_py_tuple(py))
            .unwrap()
    }
}

Now, you can use the Multiply struct to call the multiply function from your Rust code.

let multiply = Multiply::new(py);

// Call the `multiply` function and print the result.
println!("{}", multiply.multiply(py, 2, 3).extract::<i32>(py).unwrap());

This code will print 6 to the standard output, indicating that the multiply function was called successfully from Rust.

In summary, the rust-cpython crate provides a convenient and safe way to interop between Rust and C++. By using this crate, you can call Rust functions from C++ and C++ functions from Rust, allowing you to use the strengths of both languages in your projects.

Did you find this article valuable?

Support Software Engineering Blog by becoming a sponsor. Any amount is appreciated!