How to verify `extern "C"` definition signature matches implementation

Viewed 41

Given the following files:

main.rs:

mod ffi;
mod impl_do_print;

fn main() {
    unsafe {
        ffi::do_print(42.0);
    }
}

ffi.rs:

extern "C" {
    pub fn do_print(x: f32);
}

impl_do_print.rs:

#[no_mangle]
pub extern "C" fn do_print(x: i32) {
    println!("{}", x);
}

Obviously, the f32 of the definition and the i32 of the implementation don't match.

When I execute this, it prints:

1047505936

I understand that no_mangle is automatically considered unsafe, but is there any way I could ask the compiler to catch the mismatch, or would I have to write my own linter for this?

Usecase: This question came up with generated FFIs. I am able to modify the implementation in any way possible, but I cannot edit the function definition, as it is generated via bindgen.

1 Answers

So far, two potentially viable solutions were proposed:

  • Use the ffi.rs to auto-generate an implementation for the method that simply forwards the call to my own implementation. That way, the compiler would catch mismatching arguments.
  • Use the ffi.rs to generate a type definition for the function. That way, you could write a compiler check to verify that the implementation matches the definition, like this: const _: fn_type = fn_impl.

Either way, it seems to require proc macros or an external generator.


After a little more experimenting, I managed to achieve a compile time check by utilizing a macro:

impl_do_print.rs:

#[no_mangle]
pub extern "C" fn do_print(x: i32) {
    println!("{}", x);
}

macro_rules! check_implementation_type {
    ($t:ty, $name:ident) => {
        const _: $t = $name;
        const _: $t = crate::ffi::$name;
    };
}

check_implementation_type!(unsafe extern "C" fn(i32), do_print);

This still requires me to write a check_implementation_type entry for every function I implement, but it gives me a reliable compile time error if either the ffi.rs or the implementation don't match:

error[E0308]: mismatched types
  --> src/impl_do_print.rs:9:23
   |
9  |         const _: $t = crate::ffi::$name;
   |                       ^^^^^^^^^^^^^^^^^ expected `i32`, found `f32`
...
13 | check_implementation_type!(unsafe extern "C" fn(i32), do_print);
   | --------------------------------------------------------------- in this macro invocation
   |
   = note: expected fn pointer `unsafe extern "C" fn(i32)`
                 found fn item `unsafe extern "C" fn(f32) {ffi::do_print}`
   = note: this error originates in the macro `check_implementation_type` (in Nightly builds, run with -Z macro-backtrace for more info)
Related