Course contentsShow
← Rust
Lesson 716 of 1,9106. Generics and Static DispatchFree lesson

Type Parameters in FFI Boundary

Bridge the gap between generic Rust code and non-generic C interfaces.

In this lesson, you will learn how to safely cross the "language barrier" by bridging Rust’s flexible generic code with the rigid, non-generic expectations of C-style interfaces.

The Challenge of Generics at the Boundary

When you write a function with type parameters like fn process<T>(data: T), Rust uses monomorphization. It generates a unique version of that function for every concrete type you use. C, however, does not understand Rust’s generic system. If you try to expose a generic function directly via an extern "C" block, the compiler will stop you because C needs a fixed memory layout and a concrete symbol name.

To bridge this gap, you must manually "monomorphize" the boundary by creating specific, non-generic wrappers that the C code can call.

Bridging the Gap

Imagine you have a generic Rust function that works on any type implementing a trait, but you want to call it from C. You cannot make the extern "C" function itself generic. Instead, you create a concrete wrapper for each type you intend to support:

// Our generic logic
fn process_internal<T: Display>(value: T) {
    println!("{}", value);
}

// Concrete wrappers for the FFI boundary
#[no_mangle]
pub extern "C" fn process_int(val: i32) {
    process_internal(val);
}

#[no_mangle]
pub extern "C" fn process_float(val: f32) {
    process_internal(val);
}

By providing these explicit entry points, you satisfy the C linker’s need for fixed, non-generic symbols. Inside the wrapper, you simply call your generic Rust code, effectively using the boundary as a "dispatch station."

Strategic Design

When designing your FFI layer, think of these wrappers as the "static dispatch" bridge. You are essentially doing manually what the compiler usually does automatically. If you have many types to support, you can use macro-generated wrappers to reduce boilerplate, keeping your internal code generic and clean while satisfying the strict requirements of the binary interface.

Always ensure that the types passed across the boundary are #[repr(C)] or primitive types to guarantee the memory layout matches what the external caller expects.

Key Takeaway: Since C interfaces cannot understand Rust generics, you must provide concrete, non-generic wrappers for each type that needs to cross the FFI boundary.