How to use snake case names in FFI with less casting

I'm working on an FFI project, and exporting a variety of structs to C. I want C to treat these as opaque structs, so C can hold a pointer to them, but cannot access fields (because generally these are not #[repr(C)]). I also want these structs to have C-like names, using snake case instead of camel case.

Right now my code looks like below. For each MyThing I also define an opaque struct my_thing for C's point of view. When giving pointers to C, I cast from &MyThing to *const my_thing, and when receiving pointers from C, I cast from *const my_thing to to *const MyThing.

It's that latter conversion that worries me. It would be easy to accidentally cast to the wrong thing and not notice. In fact, I've already made that mistake a few times, between mut and const. Ideally I'd like some way to say "my_thing and MyThing are the same" (like a type alias) and not have to us as so much. Any thoughts or ideas about what I could be doing better here?

An additional goal is that cbindgen should not export MyThing in the generated .h file.

pub struct MyThing {
    x: f64,
    name: String,

pub struct my_thing {
    _private: [u8; 0],

pub extern "C" fn thing_new() -> *const my_thing {
    let bx = Box::new(MyThing {
        x: 0.0,
        name: "default".to_string(),
    Box::into_raw(bx) as *const _

pub extern "C" fn thing_print(thing: *const my_thing) {
    let thing: &MyThing = unsafe {
        match (thing as *const MyThing).as_ref() {
            Some(t) => t,
            None => return,
    println!("{:?}", *thing);
1 Like

It feels like macros could be a big help here, even just macro_rules!. Something like

macro_rules! cast_const_ptr {
    ( $c_type:ty -> $rust_type:ty ; $ptr:expr ) => {
            let ptr: *const $c_type = $ptr;
            ptr as *const $rust_type

would let you write cast_const_ptr!(my_thing -> MyThing; thing) and ban as in the rest of the code.

With a proc macro you could even automate the CamelCase <=> snake_case conversions but that might be a heavier approach than you prefer to use here.


Actually, what I left out in the interests of brevity is that I am already using macros:

let config: &mut ClientConfig = try_ref_from_ptr!(config, &mut ClientConfig, ());

But I have to specify the casted-to type as an argument to the macro, and if I get it wrong there is no warning. Proc macros are an interesting idea, though so far I have avoided them to minimize complexity.

1 Like

What about a trait approach?

trait CastConstPtr {
    type RustTy;
    fn cast_const_ptr(ptr: *const Self) -> *const Self::RustTy {
        ptr as *const _

Then you impl CastConstPtr once for each C type and use CastConstPtr::cast_const_ptr(thing) everywhere instead of as.


Ooh, I like this! Thank you, I'll try it.

1 Like

Here's how I wound up implementing it. Works great! Use a trait to constrain pointer casting by jsha · Pull Request #73 · abetterinternet/crustls · GitHub

1 Like