Ah yes, the rare "borrow checker is too permissive" topic.
I have a function that passes in references to FnMut
s, then internally sends them across an FFI boundary to get registered as a callback from a C function, and returns a handle. When the handle falls out of scope on Rust's end, its Drop
trait unregisters the callbacks. This works (yay) but rustc is not preventing what I think are invalid borrows. For instance, I can do this:
fn this_does_not_seem_safe() -> DeviceCallbackHandle {
let mut on_device_connect = |device_info: DeviceInfo| {
println!("{} connected", device_info.uri);
};
let mut on_device_disconnect = |device_info: DeviceInfo| {
println!("{} disconnected", device_info.uri);
};
let mut on_device_state_change = |device_info: DeviceInfo, state: DeviceState| {
println!("{} changed state: {:?}", device_info.uri, state);
};
register_device_callbacks(&mut on_device_connect, &mut on_device_disconnect, &mut on_device_state_change).unwrap()
}
Unless I'm mistaken, the closures fall out of scope at the end of the function, even though C code still references it. To convince rustc that this is the case, I think I need to tie the lifetime of the DeviceCallbackHandle
to the lifetimes of the closures, since the handle will unregister the callbacks when it drops, dropping the closure references from C code, but I don't know how.
I tried adjusting the function signature to add two lifetimes like this:
// Previously there were no lifetime annotations at all
pub fn register_device_callbacks<'cb, 'handle, F1, F2, F3>(
on_device_connected: &'cb mut F1,
on_device_disconnected: &'cb mut F2,
on_device_state_changed: &'cb mut F3
) -> Result<DeviceCallbackHandle<'handle>, Status>
where F1: FnMut(DeviceInfo), F2: FnMut(DeviceInfo), F3: FnMut(DeviceInfo, DeviceState), 'cb: 'handle {
// Some code
let closures = Box::new(ClosureStruct {
on_device_connected,
on_device_disconnected,
on_device_state_changed,
});
unsafe {
oniRegisterDeviceCallbacks(
&mut callbacks, // a struct of `extern "C"` wrapper functions, not shown
Box::into_raw(closures) as *mut _,
&mut callbacks_handle, // just ptr::null_mut()
)
}
// Some more code
Ok(DeviceCallbackHandle {
callbacks_handle,
_closures_lifetime: PhantomData,
})
}
But this backfired. rustc now believes that the handle lasts for longer than it really does, so the handle can never be valid.
fn main() {
let mut on_device_connect = // closure body
let mut on_device_disconnect = // closure body
let mut on_device_state_change = // closure body
if let Ok(handle) = register_device_callbacks(&mut on_device_connect, &mut on_device_disconnect, &mut on_device_state_change) {
println!("Got handle {:?}", handle);
loop { thread::sleep(Duration::from_millis(100)) }
}
} // error! closures dropped here while still borrowed. really?
('cb : 'handle
does mean "'cb
matches or outlives 'handle
", right?) Does anyone have a clue what the correct annotation is?