How to deal with a pointer to array of C strings in Rust?

484 Views Asked by At

I'm trying to write a Rust wrapper over the C++ library, where the pointer to array of C-style string is defined as:

char ***name;

In C++ I can iterate over strings easily:

for(int i=0;i<n;++i)
        std::cout << std::string(*(name[i])) << std::endl;

which gives correct results.

However, in rust I can't figure out how to do this correctly.

The bindings to C++ library are generated by bindgen correctly and name is presented as

pub name: *mut *mut *mut ::std::os::raw::c_char

So, I'm trying the following:

// Get a slice of pointers to strings of size n, n is known and is correct
// The type of names is &[*mut i8]
let names = unsafe{ std::slice::from_raw_parts(*(name), n) };
// Iterates over a slice. ptr: &*mut i8
for ptr in names.iter() {   
  // Constructs a c-string from *mut i8 
  let cs = unsafe{ CStr::from_ptr(*ptr) };
  // Shoud print a string, but gives rubbish instead
  println!("{:?}",cs);
}

But this prints rubbish and all lines of output are the same and of size 2, while the strings are different and of different length:

"\x90\""
"\x90\""
"\x90\""
"\x90\""
"\x90\""
"\x90\""

I'm definitely missing something obvious, but can't find any errors. Any help is appreciated!

2

There are 2 best solutions below

0
prog-fh On

Your rust code does not match the layout you describe with your C++ example. Here is a simulation of the situation, with the memory layout matching your description.

Using *(name[i]) to obtain a string on the C side tells us that name is actually an array and each element is a pointer to a char *.

I tried to mimic this layout on the Rust side. Many structs exist and each of them has a char * member. In a vector, we collect the addresses of all the char * members. Then, this vector is only known as the address of its first element and its number of elements.

Maybe the actual layout on the C side is not exactly what you described?

use std::ffi::{c_char, CStr};

#[derive(Debug)]
struct MyStruct {
    _integer: usize,
    name: *const c_char,
}

fn read_names_from_c(
    names: *const *const *const c_char,
    n: usize,
) {
    /*                           c_str
    names         name_ptr       name
    char *** •~~> [char **  •~~> char * •~~> ['o', 'n', 'e', '\0']
                  ,char **  •~~> char * •~~> ['t', 'w', 'o', '\0']
                  ,char **  •~~> char * •~~> ['t', 'h', 'r', 'e', 'e', '\0']
                  ,char **] •~~> char * •~~> ['f', 'o', 'u', 'r', '\0']
    */
    let name_slice = unsafe { std::slice::from_raw_parts(names, n) };
    for &name_ptr in name_slice.iter() {
        let name = unsafe { *name_ptr };
        let c_str = unsafe { CStr::from_ptr(name) };
        println!("{:?}", c_str);
    }
}

fn main() {
    //~~ structs with pointers to nul-terminated static strings
    let my_structs: Vec<MyStruct> = ["one\0", "two\0", "three\0", "four\0"]
        .iter()
        .enumerate()
        .map(|(i, s)| MyStruct {
            _integer: i + 1,
            name: s.as_ptr() as *const _, // u8 pointer to c_char pointer
        })
        .collect();
    //~~ collect addresses of name members
    let name_vec: Vec<*const *const c_char> =
        my_structs.iter().map(|ms| &ms.name as *const _).collect();
    //~~ pretend we only know the start address and the number of elements
    let names: *const *const *const c_char = name_vec.as_ptr();
    let n = name_vec.len();
    read_names_from_c(names, n);
}
/*
"one"
"two"
"three"
"four"
*/
0
Olaf Dietsche On

This is not an answer to your Rust problems, but an explanation about the C++ part.

Your assumption is wrong, char*** is not a pointer to an array of strings.

It is a pointer to an array of pointers to arrays of strings. The for loop as written, iterates over n arrays printing the first string of each array.


A pointer to an array of strings, or just an array of strings, would be char** or maybe char*[10].

If char*** is really the pointer you get from some library, the for loop might look like, e.g. *(name[i]) vs (*name)[i]

for (int i = 0; i < n; ++i)
    std::cout << (*name)[i] << std::endl;

Unrelated, but no need for std::string anywhere.