[Solved] How to export Vec<String> to C with ffi?


#1

Is there a way to export a Vec<String> to C (as *const *const c_char for example) with Rust ffi? I could not find any answer in the internet.

thx,
Ronny


#2

Not directly, because String is not exactly a C style string, because it lacks trailing zero. So you need to convert your Vec<String> into Vec<CString> first, that should be converted to Vec<*const c_char>, and that can be converted to *const *const c_char.


#3

I already tried this, but it seems not working:

  0 #[repr(C)]
  1 pub struct Array {
  2     len: size_t,
  3     data: *const *const c_char,
  4 }
  5     
  6 impl Array {
  7     fn from_vec(mut vec: Vec<std::string::String>) -> Array {
  8     
  9         let mut cstr_vec: Vec<CString> = vec![];
 10         for s in vec {
 11             let cstr = CString::new(s.as_str()).unwrap();
 12             cstr_vec.push(cstr);
 13         }
 14         cstr_vec.shrink_to_fit();
 15         
 16         let mut c_char_vec: Vec<*const c_char> = vec![];
 17         for s in cstr_vec {
 18             c_char_vec.push(s.as_ptr());
 19         }
 20         
 21         let array = Array {
 22             data: c_char_vec.as_ptr() as *const *const c_char,
 23             len: c_char_vec.len() as size_t,
 24         };
 25         mem::forget(c_char_vec);
 26         array
 27     }
 28 }

When I try to read it, I get just messy (however, no seg fault) strings as result.


#4

This will not work because c_char_vec will be deallocated when you return from this function so whatever you pass down to the C function will point to deallocated memory (which in some instances may work depending on the deallocator but it’s likely to break a lot)


#5

cstr_vec


#6

But that’s why I used mem::forget(c_char_vec). Shouldn’t this prevent de-allocation in Rust?


#7

Ha! You are right! I needed to reference cstr_vec in the for loop to keep it alive. Then I mem::forget(cstr_vec) and mem:forget(c_char_vec) and it worked.

Thx!


#8

Doh! Typo too early in the morning. Thanks :slight_smile:


#9

Not sure if you do that but allocating memory in Rust and then deallocating it in C is very risky and I wouldn’t recommend it.


#10

I agree. I would re-structure this code to keep the Rust data alive while you send it down to the C lib and allow the normal Rust cleanup to happen later when it’s no longer needed.


#11

Thank you for the warning.

I tried to approach this problem by giving ownership to the caller (which is actually Python).

The actual function which transports the Array to C/Python is:

 0 #[no_mangle]
  1 pub extern "C" fn standard_finder_get_standards_list(ptr: *mut Standards,
  2                                                      array: *mut Array)
  3                                                      -> c_int {
  4     let standards = unsafe {
  5         assert!(!ptr.is_null());
  6         &mut *ptr
  7     };
  8     unsafe {
  9         *array = Array::from_vec(standards.standards
 10                                           .iter()
 11                                           .map(|ref c| c.chemsc.clone())
 12                                           .collect()) as Array;
 13     }      
 14  
 15     SF_SUCCESS
 16 }

So far, it works. However, I am not quite sure if C/Python correctly frees the memory because of the two vectors cstr_vec and c_char_vec.

My guess is that if C/Python frees c_char_vec it automatically frees cstr_vec because this is were it points to.


#12

The problem is that Rust can use a different allocator than the C does. I think you can enforce Rust to use the system allocator but I would say it’s still a bit scary to use free on pointers allocated by Rust.


#13

The system allocator is not guaranteed to be malloc / free. On Windows in particular it is HeapAlloc which is not malloc.


#14

Maybe you should use an hour glass interface with C-safe types in the middle.


#15

Thanks for the hints. The hour glass interface example is great. I’ll try that.