I've written a Rust lib to serve as an FFI binding between a C program and the Rust code behind it (exported as a dylib). My initial attempts were successful, but as I get a bit deeper into my use case I've found myself stuck trying to map Rust's concept of an enum
with C's combination of struct + union
.
Here's some C code:
typedef struct Foo {
const char* name;
} Foo;
typedef struct Bar {
const char* name;
const char* field;
} Bar;
typedef struct Types {
const char* typename;
union
{
Foo foo;
Bar bar;
};
} Types;
uint8_t rust_parser(char* string, Types** types, size_t* types_len);
int main() {
Types* types = NULL;
size_t types_len = 0;
uint8_t err;
err = rust_parser("foo\nbar", &types, &types_len);
if (err > 0) {
printf("Error: Return code %d", err);
exit(err);
}
for (size_t i = 0; i < types_len; i++) {
if (types[i].typename == "foo") {
printf("a foo!");
printf("and the name is %s", types[i].foo.name);
}
else if (types[i].typename == "bar") {
printf("a bar!");
printf("and the name is %s", types[i].bar.name);
printf("and the field is %s", types[i].bar.field);
}
}
return err;
}
Corresponding Rust would be:
#[derive(Debug)]
#[repr(C)]
pub struct Foo {
name: *const c_char,
}
#[derive(Debug)]
#[repr(C)]
pub struct Bar {
name: *const c_char,
field: *const c_char,
}
pub enum Types {
FooType(Foo),
BarType(Bar),
}
impl Types {
pub fn typename(&self) -> *const c_char {
match *self {
Types::FooType { .. } => CString::new("foo").unwrap().into_raw(),
Types::BarType { .. } => CString::new("bar").unwrap().into_raw()
}
}
}
pub extern fn rust_parser(schema: *const c_char, types: *mut *mut Types, types_len: *mut size_t) -> u8 {
// Convert C string to Rust string
let c_schema = unsafe {
assert!(!schema.is_null());
CStr::from_ptr(schema)
};
let r_schema = c_schema.to_str().unwrap();
match gqlidl::parse_schema(r_schema) {
Ok(vec) => {
let mut tmp_vec: Vec<Types> = vec.into_iter().map(|mut v| {
// v.shrink_to_fit();
let s = match v.typename() {
"foo" => {
return Type::FooType(Foo::new(
v.name())
);
},
"bar" => {
return Type::BarType(Bar::new(
v.name(),
v.field())
);
},
_ => panic!("Unknown typename: {}", v.typename())
};
mem::forget(v);
s
}).collect();
tmp_vec.shrink_to_fit();
assert!(tmp_vec.len() == tmp_vec.capacity());
// Return number of types
unsafe { *types_len = tmp_vec.len() as size_t; }
// Return pointer to data
unsafe { *types = tmp_vec.as_mut_ptr(); }
// Prevent memory from being deallocated
mem::forget(tmp_vec);
0
},
Err(err) => {
writeln!(io::stderr(), "Catastrophic error: {:?}", err).unwrap();
1
}
}
}
I've cut out some of the Rust and C code out for brevity. gqlidl::parse_schema
takes a string and spits out a vector of structs. The C portion will hand the string to Rust for parsing, and then it should get back an array of various C structs and act on them. Now, I know the binding originally worked when I did not use typedef struct Types
with a union
inside. That is, I just assumed everything was of type Foo
, and the C portion could read the struct fields. But, when I try to use the program now with multiple types, I get various errors on the C side (like strings equal to (null
). That suggests that I'm reading from the wrong memory location.
Is there something obvious (or complicatedly wrong) that I am missing from the above?