Course contentsShow
← Rust
Lesson 1619 of 1,91014. System Integration and EcosystemFree lesson

Function Pointer Callbacks

You will pass Rust closures into C code using trampolines and pointer casting.

In this lesson, you will learn how to bridge the gap between Rust’s expressive closures and the rigid requirement of C-style function pointers using a technique called "trampolining."

The Impedance Mismatch

In C, a callback is typically a raw function pointer void (*callback)(void*). Because C knows nothing about Rust’s Fn traits or captured environment, you cannot pass a Rust closure directly into C code. A closure is a struct containing data and a code pointer; a C function pointer is just a code address.

To resolve this, we use a trampoline: a static, extern "C" function that acts as a middleman.

Implementing the Trampoline

When you need to pass a closure to C, you perform two steps:

  1. The Context Pointer: Cast the closure reference into a *mut c_void pointer to pass as the "user data" argument.
  2. The Trampoline: Create an extern "C" function that receives that *mut c_void, casts it back into the original closure type, and invokes it.
use std::ffi::c_void;

// The C-style callback function
extern "C" fn trampoline<F>(data: *mut c_void) 
where F: Fn() {
    // Reconstruct the closure from the pointer
    let closure = unsafe { &*(data as *const F) };
    closure();
}

fn register_callback<F: Fn()>(f: &F) {
    let ptr = f as *const F as *mut c_void;
    // Pass both the trampoline and the pointer to C
    unsafe { c_api_register(Some(trampoline::<F>), ptr) };
}

Critical Considerations

Because the C code is unaware of Rust's ownership model, the closure must outlive the registration lifetime. If the C library stores this pointer for later execution, you must ensure the closure data resides in a stable memory location (like the heap via Box::into_raw) and is managed manually to avoid dangling pointers.

The trampoline is essentially an "adapter" that strips away the high-level closure semantics, allowing Rust to satisfy the C ABI while retaining the ability to execute the captured environment once control returns to the Rust side.

Key Takeaway: To pass a closure to C, you must pack the closure as a *mut c_void and provide a static extern "C" trampoline function to cast and execute that pointer safely.