I've been learning Rust for about two weeks now and today, I got into its FFI. I used Python to play with Rust, using ctypes and libc. I passed integers, strings and even learned to pass a list of integers (thanks to this wonderful answer).
Then, I tried to pass a list of strings (following the reasoning behind the that answer), but I failed, as I couldn't get a lead on it. In Python, I have something like this to pass the array of strings.
def testRust():
lib = ctypes.cdll.LoadLibrary(rustLib)
list_to_send = ['blah', 'blah', 'blah', 'blah']
c_array = (ctypes.c_char_p * len(list_to_send))()
lib.get_strings(c_array, len(list_to_send))
In Rust, I thought that there should be something (like a STRING_RECEIVER
) to collect the incoming strings, but I can't find one.
#![feature(libc)]
extern crate libc;
use std::slice;
use libc::{size_t, STRING_RECEIVER};
#[no_mangle]
pub extern fn get_strings(array: *const STRING_RECEIVER, length: size_t) {
let values = unsafe { slice::from_raw_parts(array, length as usize) };
println!("{:?}", values);
}
Is there any alternative way to achieve this?
There is absolutely no difference with the case of array of numbers. C strings are zero-terminated arrays of bytes, so their representation in Rust will be *const c_char
, which could then be converted to &CStr
which then can be used to obtain &[u8]
and then &str
.
Python:
import ctypes
rustLib = "libtest.dylib"
def testRust():
lib = ctypes.cdll.LoadLibrary(rustLib)
list_to_send = ['blah', 'blah', 'blah', 'blah']
c_array = (ctypes.c_char_p * len(list_to_send))(*list_to_send)
lib.get_strings(c_array, len(list_to_send))
if __name__=="__main__":
testRust()
Rust:
#![feature(libc)]
extern crate libc;
use std::slice;
use std::ffi::CStr;
use std::str;
use libc::{size_t, c_char};
#[no_mangle]
pub extern fn get_strings(array: *const *const c_char, length: size_t) {
let values = unsafe { slice::from_raw_parts(array, length as usize) };
let strs: Vec<&str> = values.iter()
.map(|&p| unsafe { CStr::from_ptr(p) }) // iterator of &CStr
.map(|cs| cs.to_bytes()) // iterator of &[u8]
.map(|bs| str::from_utf8(bs).unwrap()) // iterator of &str
.collect();
println!("{:?}", strs);
}
Running:
% rustc --crate-type=dylib test.rs
% python test.py
["blah", "blah", "blah", "blah"]
And again, you should be careful with lifetimes and ensure that Vec<&str>
does not outlive the original value on the Python side.