[feature] Add support for reverse bindgen function implementation
Problem
The goal is to have c headers be the source of truth and then have bindgen generate function signatures that can be implemented by rust code.
the following code is generated by this header
/**
* @brief Set the last error message
* @param message Error message
*/
void error_set_last_message(const char* message);
/**
* @brief Get the last error message
* @return Last error message or NULL if no error occurred
*/
const char* error_last_message();
unsafe extern "C" {
#[doc = " @brief Set the last error message\n @param message Error message"]
pub fn error_set_last_message(message: *const ::std::os::raw::c_char);
}
unsafe extern "C" {
#[doc = " @brief Get the last error message\n @return Last error message or NULL if no error occurred"]
pub fn error_last_message() -> *const ::std::os::raw::c_char;
}
I would like bindgen to generate something like the following
pub trait error_api {
#[doc = " @brief Set the last error message\n @param message Error message"]
unsafe extern "C" fn error_set_last_message(message: *const ::std::os::raw::c_char);
#[doc = " @brief Get the last error message\n @return Last error message or NULL if no error occurred"]
unsafe extern "C" fn error_last_message() -> *const ::std::os::raw::c_char;
}
With some sort of builder setting like
let bindings = bindgen::builder()
.function_impl_trait("error_.*", "error_api");
Then using this would be like
pub struct Extern;
impl error_api for Extern {
#[unsafe(no_mangle)]
unsafe extern "C" fn error_set_last_message(message: *const ::std::os::raw::c_char) {
...
}
#[unsafe(no_mangle)]
unsafe extern "C" fn error_last_message() -> *const ::std::os::raw::c_char {
...
}
}
Motivation
A lot of code today is written in c++ / c with a public c-api that is exported so that other languages / customers can interact with the underlying code without having its source. In an effort to move more code over to rust having some way of using the c headers as a source of truth would make it easier to incrementally port legacy code bases over to rust.
Alternative solutions
-
Using a tool like cbindgen to generate the header files from the rust code. While this solution is good i would still prefer to have the headers be the source of truth for the definitions of the functions.
-
Bindgen can be extended to generate something like the following instead of the extern "C" blocks.
#[doc = " @brief Set the last error message\n @param message Error message"]
#[repr(transparent)]
pub struct error_set_last_message(pub unsafe extern "C" fn(message: *const ::std::os::raw::c_char));
#[doc = " @brief Get the last error message\n @return Last error message or NULL if no error occurred"]
#[repr(transparent)]
pub struct error_last_message(pub unsafe extern "C" fn() -> *const ::std::os::raw::c_char);
And then its up to the library author to implement and expose these functions and can use these types to validate they are the same as the header. Also has the advantage of allowing bindgen to be used with dlopen
Related https://github.com/rust-lang/rust/issues/58493
After experimenting with a way to verify the bindings and the function implementation match I discovered this trick for type coercion
macro_rules! assert_same_sig {
($f1:expr, $f2:expr) => {
const _: () = {
[$f1, $f2];
};
};
}
#[unsafe(no_mangle)]
unsafe extern "C" fn pv_error_last_message() -> *const c_char {
...
}
assert_same_sig!(error_last_message, sdk_sys::error_last_message);
It turns out putting 2 functions of the same signature into an array literal causes the compiler to coarse both types into their function pointers. Normally this compression is not possible because each function has its own type and you cant compare them.
Yeah, what we do in the Linux kernel to check that we have the right signature is to use an #[export] macro that adds no_mangle plus a similar check:
const _: () = {
if true {
::kernel::bindings::#name
} else {
#name
};
};
https://rust.docs.kernel.org/macros/attr.export.html