How to return a vector from c back to rust [FFI]

Hi,

I've been struggling with returning a vec back from my c++ library. The situation is the following:

//rust

fn query (
        res: *const libc::c_uchar,
        rln: libc::c_ulong
    );


...


let result = unsafe{
            query(
                res.as_mut_ptr(),
                rln as libc::c_ulong
            );
            slice::from_raw_parts(res, rln)
}

//c++


void query ( uchar* res, unsigned long rln) {

      rln = 20;
      vector<uchar> tres(rln); 
      for (unsigned long i =0; i< rln; i++){
         res[i] = 'x';
      }
      res = &tres[0];
}

it seams that whenever i use this approach my tres goes out of scope and i am left with blank res vector... What would be the correct way to do this ? (to return a vector from c back to rust)

Thank you :slight_smile:

I suspect you'd be better served by allocating the memory in Rust and then passing a pointer to that memory over FFI to be filled in by the C code. Juggling ownership of collections across FFI is complicated, as you're finding out here.

1 Like

but what if i cannot know how much memory will i need to pre-allocate... my example is a dummy one just to illustrate a minimum set of requirements... and for some reason i cannot find anything similar on the net... very frustrating, this problem, is :slight_smile:

You have to allocate the memory in the same language as where you are going to deallocate it in. Additionally the C++ vector type is not compatible with the Rust Vec type.

You can provide extern Rust functions that allow creation and reallocation of a Rust Vec, and call those from C++ to manage the memory. You can pass the vector to C++ by using the triplet found in the Vec::from_raw_parts method.

1 Like

Have the c code provide the following:

  • An output_t struct that has a pointer and length.
  • Your function, which returns (or initializes) an output_t.
  • Functions for accessing the pointer and length.
  • A function for cleaning up an output_t.
2 Likes

Protip: C++ std::vector<T>'s size is vary across platforms. It's 3-ptr-wide on gcc and clang, but 4-ptr-wide on msvc. Oh, and have you heard about the std::vector<bool>?

1 Like

C++

extern "C" {
    void query (uchar * * res, size_t * rln)
    {
        *rln = 20;
        *res = new uchar[*rln]; 
        for (size_t i = 0; i < rln; ++i) {
            (*res)[i] = 'x';
        }
    }

    void free_res (uchar * res)
    {
        delete[] res;
    }
}

Rust

mod ffi {
    use ::libc::{c_uchar, size_t};

    extern "C" {
        fn query (res: &mut *mut c_uchar, rln: &mut size_t)
        ;
    }

    // Create safe API around this:
    use ::core::{
        convert::TryInto,
        ops::{Deref, DerefMut},
        ptr,
        slice,
    };

    pub(in super)
    struct CppBoxedSlice {
        ptr: ptr::NonNull<c_uchar>,
        len: usize,
    }

    impl CppBoxedSlice {
        #[inline]
        pub
        fn from_query () -> Self
        {
            let mut res: *mut c_uchar = ptr::null_mut();
            let mut rln: size_t = 0;
            unsafe { query(&mut res, &mut rln); }
            let ptr: ptr::NonNull<c_uchar> =
                ptr::NonNull::new(ptr)
                    .expect("Error from `query()`: got NULL ptr")
            ;
            let len: usize =
                len .try_into()
                    .expect("Error from `query()`: `rln` overflowed")
            ;
            Self { ptr, len }
        }
    }

    impl Drop for CppBoxedSlice {
        fn drop (self: &'_ mut Self)
        {
            unsafe { free_res(self.ptr.as_ptr()); }
        }
    }

    impl Deref for CppBoxedSlice {
        type Target = [c_uchar];

        #[inline]
        fn deref (self: &'_ Self) -> &'_ Self::Target
        { unsafe {
            slice::from_raw_parts(self.ptr.as_ptr(), self.len)
        }}
    }
    impl DerefMut for CppBoxedSlice {
        #[inline]
        fn deref_mut (self: &'_ mut Self) -> &'_ mut Self::Target
        { unsafe {
            slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len)
        }}
    }
}
// pub
use ffi::CppBoxedSlice;

let mut result = CppBoxedSlice::from_query(); // <- Owns the allocation -+
let slice: &mut [c_uchar] = &mut *result;                             // |
/* result.drop(); */ // <-- calls free_res() to deallocate --------------+
1 Like

Thank you all !!!!!

1 Like