Basic Rust and C++ InterOp
Table of contents
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.