FFI allocating and returning array of c strings


I am trying to implement a function in Rust that is called from C and that returns an array of null terminated strings. The array must be allocated by Rust and must remain valid until the rust library is called the next time.

I am aware of the methods for converting between rust and c strings, but I can't quite wrap my head around doing this for an array/vector. Are there any build in functions for doing this, or do I have to roll out my own solution? If so can anyone guide me in the right direction?

Any help is much appreciated :slight_smile:

You will need to turn your vector into a pointer and ensure it's not dropped.
See https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d0e44ce1f765ce89523ef89ccd864e54

1 Like

Using safer_ffi:

use ::safer_ffi::prelude::*;

/// Some doc.
/// \remark the obtained `Vec` must be freed using `free_rust_vec`.
fn foo () -> repr_c::Vec<char_p::Box>
    let mut vec: Vec<char_p::Box> = vec![];
        char_p::new("Hello, World!") // Panics (and aborts) if inner null byte.
    vec.into() // Transform the Rust `Vec` into a `#[repr(C)]` one.

/// Frees a single Rust-allocated C string.
fn free_rust_string (string: char_p::Box)

/// Frees the input `Vec`,
/// which recursively calls `free_rust_string` on its first `.len` elements.
fn free_rust_vec (vec: repr_c::Vec<char_p::Box>)

which will auto-generate the following header definition:

/** \brief
 *  Same as [`Vec<T>`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout
typedef struct {

    char * * ptr;

    size_t len;

    size_t cap;

} Vec_char_ptr_t;

/** \brief
 *  Some doc.
 *  \remark the obtained `Vec` must be freed using `free_rust_vec`.
Vec_char_ptr_t foo (void);

/** \brief
 *  Frees a single Rust-allocated C string.
void free_rust_string (
    char * string);

/** \brief
 *  Frees the input `Vec`,
 *  which recursively calls `free_rust_string` on its first `.len` elements.
void free_rust_vec (
    Vec_char_ptr_t vec);
1 Like

Thank you for the code snippet, this is very close to I am looking for.
A small detail is that the pointer must be passed by reference rather than as a result.

Unfortunately I am unable to make argument point to the newly allocated memory.

cannot assign to `values`, as it is not declared as mutable

The argument is declared as mutable, so it seems there is a semantic difference between raw pointers in rust and C pointers. How would i change the address of double pointer values in rust?

pub extern "C" fn fmi2GetString(
    c: *const i32,
    vr: *const c_uint,
    nvr: usize,
    values: *mut *mut c_char,
) -> c_int {
    let get_string = || -> Result<i32, Error> {
        let references = unsafe { std::slice::from_raw_parts(vr, nvr as usize) }.to_vec();
        let h = unsafe { *c };

        let gil = Python::acquire_gil();
        let py = gil.python();

        // TODO replace with "map_error"
        let (values_vec, status): (Vec<String>, i32) = SLAVE_MANAGER
            .call_method1(py, "get_xxx", (h, references))

        values = unsafe { vector_to_string_array(values_vec) }; // returns *mut *mut c_char



    match get_string() {
        Ok(s) => s,
        Err(e) => {
            println!("{}", e);

If you want to write to the pointee, you need to use the dereference operator.

unsafe {
    *values = vector_to_string_array(values_vec);

It may not be clear from the code itself, values pointer can be null.
Would using the dereferencing operator and assigning to this not cause a segfault?

Never dereference a pointer like that. Dereferencing will try to read the previous value and drop it. If it is a pointer that might be not to critical (pointers don't implement Drop), but in other cases it might be problematic (e.g. Bug #1: Destructors running after assignment (double free))

Rust is correct here: cannot assign to values, as it is not declared as mutable

values is defined as values: *mut *mut c_char. That is: mutable pointer to mutable pointer to a c_char. values itself (the binding) is immutable, which is why you can't assign to it.

What you will need is values: *mut *mut *mut c_char, so that you then can write into that to point to your vector.

ptr::write(results, out.as_mut_ptr())

On the C side it's called as:

char **values;
int len = fmi2GetString(c, vr, nvr, &values);

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.