Separate Data and Code When Passing Rust Closures to C

2026-08-30 浏览 (1)


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 wrappers
  • ffi-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 for Uninitialized Memory

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

Choose Appropriate Data Layout for Struct/Tuple/Enum

  • 所属分类: AI
  • 本文标签: rust
  • 版权声明: 本文链接 https://seaxiang.com/blog/VE6SCMxX