Translating FFI signatures from C to Rust


#1

I’m making a Rust wrapper around a C interface and I’m not 100% sure on how to translate the function and struct signatures into Rust-speak.

In C-land we have this snippet from the header file:

struct _Device;
typedef _Device* DeviceHandle;

Status deviceGetInfo(DeviceHandle device, DeviceInfo* pInfo);

I’m interpreting that as DeviceHandle being a pointer to a do-nothing struct that only exists as a marker. This is my best guess at how to call this in Rust:

// ffi.rs
#[repr(C)]
pub struct FfiDevice {}
pub type FfiDeviceHandle = *const FfiDevice;

extern {
    // One of many functions that needs a device handle
    pub fn ffiDeviceOpen(device: FfiDeviceHandle) -> c_int;
}

// lib.rs
pub struct Device {
    device: FfiDevice,
}

impl Device {
    pub fn open(&self) -> Status {
        unsafe { ffiDeviceOpen(&self.device) }
  }
}

Does this pass a sanity check? The type checker says it’s good, but I don’t know if the handle is being correctly passed as a pointer because when I call extern functions that require handles I get garbage back.


#2

If that’s literally it, then yeah, it’s an opaque marker. The way you could model that in Rust is via an empty enum:

pub enum FfiDevice {}
pub type FfiDeviceHandle = *const FfiDevice;

That’s slightly better than a struct because the above enum is “uninhabited”, and thus no instances can be created (whereas the struct can have an instance).

Shouldn’t that be device: FfiDeviceHandle?


#3

I think you need to show more code. In particular, struct _Device; is not a marker type but rather how you define an opaque type in C. That is, the memory representation of a _Device is not part of the public interface of the library you’re wrapping. In Rust speak, I’d probably write it like this:

pub type FfiDevice = c_void;
pub type FfiDeviceHandle = *const FfiDevice;

The code I’d like to see is a complete example that reproduces your issue.


#4

Okay, problem solved, thanks both of you. I didn’t know this thing was called an opaque type, so that gave me a lot of info after I knew what to search for.

The problem was me intuiting the wrong usage of the handle. I thought that the C code would identify handles by the things they point to, which is why I was storing a FfiDevice inside my struct, and passing &self.device to each param that expected a FfiDeviceHandle. It was the right type, but it was a different pointer for each &self.device when the C code expected the same pointer each time, hence the garbage I was getting when I tried to reuse it.

The working code:

pub struct Device {
    device: FfiDeviceHandle,
}

impl Device {
    pub fn new() -> Self {
        Device {
            device: ptr::null(), // was FfiDevice {};
        }
    }

    pub fn open(&self) -> Status {
        unsafe { ffiDeviceOpen(self.device ) }
    }
}

#5

Awesome, glad you got it figured out. Thank you for posting the working code. :slight_smile: