I'm currently tinkering with interfacing between Python and Rust for a hobby. (Clearly I have too much time on my hands.) The problem I'm working on right now is passing large integers between the two languages.
Passing large integers from Python to Rust was suprisingly straighforward. You convert the large integer into an array of 32-bit integers, and Rust is commendably laid back about the whole thing. The problem is going the other way: passing a large integer from Rust to Python. Rust really doesn't like passing arrays to external interface!
A solution which I have found, which some may find hacky but which I find acceptable, is to convert the large integer into a string, and then pass that string to Python, which will then convert it back into an integer. That seems to function, but, on the basis of Vladimir Matveev's answer to this question, I'm worried that I may be introducing memory leaks. Am I?
(Note that, if I try to call the free
function that Vladimir Matveev recommends immediately after receiving the string from Rust, it gives me a core dumped
fault.)
This is what my Rust code looks like:
const MAX_DIGITS: usize = 100;unsafe fn import_bigint(digit_list: PortableDigitList) -> BigInt { let digits = Vec::from(*digit_list.array); let result = BigInt::new(Sign::Plus, digits); return result;}fn export_bigint(n: BigInt) -> *mut c_char { let result_str = n.to_str_radix(STANDARD_RADIX); let result = CString::new(result_str).unwrap().into_raw(); return result;}#[repr(C)]pub struct PortableDigitList { array: *const [u32; MAX_DIGITS]}#[no_mangle]pub unsafe extern fn echo_big_integer( digit_list: PortableDigitList) -> *mut c_char { let big_integer = import_bigint(digit_list); println!("Received big integer: {:?}. Sending it back...", big_integer); return export_bigint(big_integer);}
Someone requested a MWE. Here it is. This is my lib.rs
:
use std::ffi::CString;use libc::c_char;#[no_mangle]pub unsafe extern fn return_string() -> *mut c_char { let result_string = "4294967296"; println!("Returning string to Python: {}", result_string); let result = CString::new(result_string).unwrap().into_raw(); return result;}#[no_mangle]pub unsafe extern fn free_string(pointer: *mut c_char) { let _ = CString::from_raw(pointer);}
And this is my Cargo.toml:
[package]name = "return_string"version = "0.1.0"edition = "2021"[lib]name = "return_string"crate-type = ["dylib"]# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]libc = "0.2.152"
Using cargo build --release
, I then compile this into an .so file, which is what I call from Python, like so:
from ctypes import CDLL, c_char_p, castlib = CDLL("return_string/target/release/libreturn_string.so")rust_func = lib.return_stringrust_func.restype = c_char_presult_pointer = rust_func()result = cast(result_pointer, c_char_p).value.decode()print("Rust returned string: "+result)free_func = lib.free_stringfree_func.argtypes = [c_char_p]free_func(result_pointer)
If I run the above Python script, it gives me this output:
Returning string to Python: 4294967296Rust returned string: 4294967296munmap_chunk(): invalid pointerAborted (core dumped)