Rust &[u8] slice to C++ uint8_t* buffer (slice to raw parts)

I'm filling a Rust Vec of u8 and I want to pass as an argument to C++ code as a uint8_t* buffer. Technically doing a slice would work if I am careful about its memory layout, but is there a safe way of doing so?

I'm still not sure about using Vec though, because its slice object could be bigger than the data stored

Bridging using CXX is going to be the safest way (it still has you writing unsafe on the module as a recognition that c++ code is inherently unsafe). Using it, you should be able to expose the rust slice as a C++ type, on which you will be able to take a data pointer. Upholding the lifetime requirements on the c++ side is up to you

1 Like

More generally —that is, beyond C++, e.g., C, or other languages whose FFI is built atop C (which, incidentally, includes C++ itself :grinning_face_with_smiling_eyes:—, you can use

image link

and its c_slice::Ref<'_, u8> (you can .into() and &'_ [u8]) to expose (e.g., through #[ffi_export]) it over the FFI: on the C-ish side, you'll have access to a:

typedef struct {
    uint8_t const * ptr; // non-NULL
    size_t len;
} slice_ref_uint8_t;

definition.

1 Like

Are you wanting to give the C++ code ownership of this slice, or just let it access the elements in the buffer?

If it's the latter, you should be able to store the slice somewhere (e.g. in a Rust object or variable on the stack) then pass a pointer to the start of the buffer + length to the C++ code.

use std::os::raw::c_int;

extern "C" {
  fn some_cpp_function(buffer: *const u8, length: c_int);
}

fn my_rust_code() {
  let buffer: Vec<u8> = ...;

  unsafe {
    some_cpp_function(buffer.as_ptr(), buffer.len() as c_int);
  }
}

This is effectively what the c_slice::Ref<u8> from @Yandros's solution does, except they bundle the buffer and length up into a struct and use convenience methods and macros to help add a bit more safety.

If you want to give the C++ code ownership of this Vec<u8> then things get more complicated because C++ (or any other language, for that matter) doesn't understand how to pass a Vec around or invoke its destructors.

Using vanilla Rust, some possible solutions are:

  1. Put the Vec<u8> behind a level of indirection (e.g. Box<Vec<u8>>) and force C++ to pass around and call functions with *mut Vec<u8>, or
  2. Copy the elements into a buffer that C++ owns so there is no confusion around ownership (i.e. first C++ asks for the Vec's length, then it allocates a std::vector<uint8_t> with the appropriate size, then it passes a pointer to the start of that C++ buffer to some fill_buffer() function exposed by Rust)

Alternatively, you can use a framework like cxx that is a lot more opinionated and powerful. In which case you can pass a Rust Vec<T> by valueacross the FFI boundary and it will be automatically converted to the vec template class (probably using Vec::from_raw_parts()).

1 Like

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.