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

Advanced FFI with Generics

Implement complex data exchanges across language boundaries using generic type markers.

In this lesson, you will learn how to use generic type markers to safely orchestrate complex data exchanges between Rust and foreign languages like C.

When working with FFI (Foreign Function Interface), we often face the challenge of passing generic Rust structures to C code that only understands raw pointers and fixed memory layouts. If we try to pass a Box<T> or a generic Vec<T> directly, the ABI (Application Binary Interface) mismatch will cause a crash.

To bridge this gap, we use Generic Type Markers. These are zero-sized marker types that act as "contracts" for the foreign code. By defining a generic wrapper, we can tell the Rust compiler to enforce safety while the foreign language treats the data as an opaque handle.

Defining a Generic FFI Handle

Consider a scenario where you want to pass different types of data buffers to a C library without losing type safety on the Rust side:

// A marker struct that ensures the foreign code knows what it's holding
#[repr(C)]
pub struct ForeignHandle<T> {
    data: *mut T,
    len: usize,
}

// We implement an interface that provides a stable way for C to interact
// with this generic structure.
impl<T> ForeignHandle<T> {
    pub fn as_ptr(&self) -> *const T { self.data }
    
    pub fn export_raw(mut self) -> *mut T {
        let ptr = self.data;
        std::mem::forget(self); // Prevent Rust from deallocating
        ptr
    }
}

By parameterizing ForeignHandle<T>, we ensure that the compiler tracks the underlying type T even when the data is sitting in a C-allocated buffer. When passing this across the FFI boundary, we use the #[repr(C)] attribute to guarantee the memory layout is predictable.

The power here is in the Static Dispatch: because the FFI methods are generic, the compiler generates specific, optimized versions of these functions for every type T you use. This avoids the overhead of runtime type checking or dynamic dispatch, providing a seamless, high-performance bridge that maintains Rust's strict safety guarantees while satisfying the requirements of the foreign environment.

Key Takeaway: Use generic marker types with #[repr(C)] to maintain type-safe data boundaries and enable static dispatch when interfacing with foreign code.