id: ffi-16 original_id: P.UNS.FFI.16 level: P impact: HIGH
Separate Data and Code When Passing Rust Closures to C
Summary
C callbacks are function pointers without captured state. To pass Rust closures to C, separate the function pointer from the closure data using a "trampoline" pattern.
Rationale
- Rust closures can capture state (like lambdas)
- C function pointers are just addresses, no state
- Must pass state separately via
void*user_data
Bad Example
// DON'T: Try to pass closure directly
extern "C" {
fn set_callback(cb: fn(i32) -> i32); // Only works for non-capturing!
}
fn bad_closure() {
let multiplier = 2;
let closure = |x| x * multiplier; // Captures multiplier
// This won't compile - closure is not fn pointer
// set_callback(closure);
}
// DON'T: Transmute closure to function pointer
fn bad_transmute() {
let closure = |x: i32| x * 2;
let fp: fn(i32) -> i32 = unsafe { std::mem::transmute(closure) };
// UB: Closure may have non-zero size
}
Good Example
use std::os::raw::c_void;
use std::ffi::c_int;
// C callback signature with user_data
type CCallback = extern "C" fn(value: c_int, user_data: *mut c_void) -> c_int;
extern "C" {
fn set_callback(cb: CCallback, user_data: *mut c_void);
fn remove_callback();
}
// DO: Use trampoline pattern
fn good_closure<F: FnMut(i32) -> i32>(mut closure: F) {
// Trampoline function that forwards to the closure
extern "C" fn trampoline<F: FnMut(i32) -> i32>(
value: c_int,
user_data: *mut c_void,
) -> c_int {
let closure = unsafe { &mut *(user_data as *mut F) };
closure(value as i32) as c_int
}
let user_data = &mut closure as *mut F as *mut c_void;
unsafe {
set_callback(trampoline::<F>, user_data);
// Important: closure must live until callback is removed!
}
}
// DO: Box the closure for 'static lifetime
struct CallbackHandle {
closure: Box<dyn FnMut(i32) -> i32>,
}
impl CallbackHandle {
fn new<F: FnMut(i32) -> i32 + 'static>(closure: F) -> Self {
Self { closure: Box::new(closure) }
}
fn register(&mut self) {
extern "C" fn trampoline(value: c_int, user_data: *mut c_void) -> c_int {
let closure = unsafe { &mut *(user_data as *mut Box<dyn FnMut(i32) -> i32>) };
closure(value as i32) as c_int
}
let user_data = &mut self.closure as *mut _ as *mut c_void;
unsafe { set_callback(trampoline, user_data); }
}
}
impl Drop for CallbackHandle {
fn drop(&mut self) {
unsafe { remove_callback(); }
// Now safe to drop closure
}
}
// Usage
fn example() {
let multiplier = 2;
let mut handle = CallbackHandle::new(move |x| x * multiplier);
handle.register();
// handle must live until callback is no longer needed
}
Trampoline Pattern
Rust Closure: |x| x * captured_value
|
v
+-----------------+ +-----------------+
| trampoline fn | --> | closure data |
| (no captures) | | (captured_value)|
+-----------------+ +-----------------+
| ^
| user_data ptr |
+-------------------------+
C sees: function pointer + void* user_data
Checklist
- Does my closure capture any state?
- Am I using the trampoline pattern?
- Does the closure data live long enough?
- Am I unregistering before dropping the closure?
Related Rules
ffi-03: Implement Drop for resource wrappersffi-10: Thread safety for callbacks
你可能感兴趣的文章
Do Not Expose Raw Pointers in Public APIs
Unsafe Code Authors Must Verify Safety Invariants
Ensure Consistent Data Layout for Custom Types
Add Safety Section in Docs for Public Unsafe Functions
Do Not Use Union Variants Across Different Lifetimes
Use MaybeUninit
Ensure C-ABI Compatibility for Strings Between Rust and C
Do Not Dereference Pointers Cast to Misaligned Types
Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types