I'm using this technique for C code, called from Rust, call a Rust's method back.
pub type OnVpnLog = std::sync::Arc<std::sync::Mutex<dyn Fn(*const libc::c_char) + Send + Sync>>;
pub type OnVpnSomething = std::sync::Arc<std::sync::Mutex<dyn Fn(i32) + Send + Sync>>;
struct OVPNClientInner {
on_vpn_log: Option<OnVpnLog>,
on_vpn_something: Option<OnVpnSomething>,
}
unsafe extern "C" fn on_log_trampoline(
buffer: *const libc::c_char,
user_data: *mut libc::c_void,
) -> libc::c_int {
let ovpn_client_inner = &mut *(user_data as *mut OVPNClientInner);
(ovpn_client_inner.on_vpn_log.as_ref().unwrap().lock().unwrap())(buffer);
0
}
unsafe extern "C" fn on_something_trampoline(
user_data: *mut libc::c_void,
) -> libc::c_int {
let ovpn_client_inner = &mut *(user_data as *mut OVPNClientInner);
(ovpn_client_inner.on_vpn_something.as_ref().unwrap().lock().unwrap())(0);
0
}
In this specific case, I'm calling on_vpn_log
which is behing a Mutex
. However, this is not sufficient. Suppose that I call on_vpn_log
while the function on_vpn_log
is being replaced by another one. This would lead to undefined behaviour.
Also, the 2 trampolines could in the future with a code update try to access something inside OVPNClientInner
. We must ensure that OVPNClientInner
is accessed only once at a time.
One option would be a global static mutex, but that would block one instance of OVPNClientInner
while another was being updated.
Another option would be to put a mutex inside OVPNClientInner
:
struct OVPNClientInner {
on_vpn_log: Option<OnVpnLog>,
on_vpn_something: Option<OnVpnSomething>,
mutex: Mutex
}
But OVPNClientInner
is shared between C and Rust, so no ffi-unsafe types can be present. Also that mutex could be changed while we try to lock it (maybe we could make it immutable, I don't know)
What would be a good solution here to provide good safety?