12

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?

1 Answer 1

12

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.

Sign up to request clarification or add additional context in comments.

2 Comments

This is pretty awesome! I never got an idea that we could do something like *const *const c_char. Thanks for the quick answer :)
note that feature(libc) can go away by using the libc crate from crates.io

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.