Hello,
I need to manipulate a Rust ID type from C, which looks like this:
#[repr(transparent)]
pub struct RustId(pub NonZeroU128);
Since u128
is not FFI-safe, and since I'd like to avoid allocating memory everywhere if possible, I've created these helper structs and functions:
#[repr(C)]
pub struct c_id {
_data: [u8; 16],
}
#[repr(C)]
pub struct opt_c_id {
pub has_value: bool,
pub value: c_id,
}
#[no_mangle]
pub extern "C" fn c_id_new(value: u64) -> opt_c_id {
match NonZeroU128::new(value as u128) {
Some(value) => opt_c_id {
has_value: true,
value: unsafe { mem::transmute::<NonZeroU128, c_id>(value) },
}
None => opt_c_id {
has_value: false,
value: c_id { _data: [0; 16] },
}
}
}
#[no_mangle]
pub extern "C" fn c_id_display(id: c_id) {
// Probably check to see if the array is full of zeroes here.
let id = unsafe { mem::transmute::<c_id, RustId>(id) };
println!("{}", id.0);
}
I can then make use of this API like so:
#include "id.h"
int main() {
opt_c_id opt_id = c_id_new(42);
if (opt_id.has_value) {
c_id id = opt_id.value;
c_id_display(id);
}
return 0;
}
But is it actually safe? Is this approach valid or is there another way? I will have a lot of these types around, so allocating memory each time to then cast to *mut c_void
seems bad to me.
For full reference, here is the header file as generated by cbindgen:
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
typedef struct c_id {
uint8_t _data[16];
} c_id;
typedef struct opt_c_id {
bool has_value;
struct c_id value;
} opt_c_id;
struct opt_c_id c_id_new(uint64_t value);
void c_id_display(struct c_id id);
Thanks in advance for your replies.