Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust interop with C++ std::string

I'm trying to build Octave functions in Rust. Octave's API is in C++, so I've generated bindings using rust-bindgen. I'm currently working through the problems that occur when trying to generate bindings that include std::string. It would be nice if I could leave it opaque and valid pointer to a C++ std::string. Would it be possible to build a utility function on the C++ side any time I needed to pass in a C++ std::string?

I was naive when I first attempted this. It is clearly wrong. A Rust std::ffi:CString is for C strings, not C++ strings. I found this recent blog helpful when comparing the two. My first attempt looks like this:

#![allow(non_snake_case)]
#![allow(unused_variables)]

extern crate octh;

// https://thefullsnack.com/en/string-ffi-rust.html
use std::ffi::CString;

#[no_mangle]
pub unsafe extern "C"  fn Ghelloworld (shl: *const octh::root::octave::dynamic_library, relative: bool) -> *mut octh::root::octave_dld_function {
    let name = CString::new("helloworld").unwrap();
    let pname = name.as_ptr() as *const octh::root::std::string;
    std::mem::forget(pname);

    let doc = CString::new("Hello World Help String").unwrap();
    let pdoc = doc.as_ptr() as *const octh::root::std::string;
    std::mem::forget(pdoc);

    octh::root::octave_dld_function_create(Some(Fhelloworld), shl, pname, pdoc)
}    

pub unsafe extern "C" fn Fhelloworld (args: *const octh::root::octave_value_list, nargout: ::std::os::raw::c_int) -> octh::root::octave_value_list {
    let list_ptr = ::std::ptr::null_mut();
    octh::root::octave_value_list_new(list_ptr);
    ::std::ptr::read(list_ptr)
}

I need to pass in the function name and documentation as strings to octave_dld_function_create. I wish there was a CppString that I could use instead. Any suggestions on how to proceed?

like image 291
Cameron Taggart Avatar asked Oct 02 '17 11:10

Cameron Taggart


Video Answer


1 Answers

This is a classic FFI issue and the solution is to use a "hour-glass" design: Language A <=> Common ABI <=> Language B.

It could be possible, of course, to evolve bindgen so that it can faithfully reproduce a C++ ABI, but in practice it would require a full C++ compiler which is probably too much effort.

Using a "hour-glass" design, each of the languages with a difficult ABI use their own specialized toolchain to convert to a specific well-known ABI. In this case, it would be C++ <=> C <=> Rust.

A possible solution is to create a C wrapper library around the C++ API, and then use bindgen on that. This is what the LLVM and Clang projects do.

It is the simplest solution, and the Octavo project may very well be willing to integrate such an octavo-c facade in-tree (which is always best to guarantee it's up-to-date).


Another solution would be to create a C++ companion library for bindgen, which takes care of providing a C-ABI for common C++ types (such as std::string). This would be a more difficult endeavor, especially since:

  • C has no generics, thus C++ templates would have to be either out-of-scope, or pre-instantiated templates would have to be wrapped one at a time,
  • C does not know how to invoke move or copy constructors, so unless the C++ types are already PODs, they have to be manipulated through opaque pointers,
  • C does not know ...
like image 105
Matthieu M. Avatar answered Nov 08 '22 18:11

Matthieu M.