Course contentsShow
← Rust
Lesson 1558 of 1,91013. Metaprogramming with MacrosFree lesson

Zero-Cost Abstractions for FFI

You will automate the generation of C bindings to ensure type safety across language boundaries.

In this lesson, you will learn how to leverage procedural macros to automate the generation of type-safe wrappers for C-FFI, ensuring zero runtime overhead.

When interacting with C via FFI, we often deal with raw pointers, void* types, and manual memory management. A manual, idiomatic Rust wrapper (using Drop for cleanup and Result for error codes) is safe, but writing these for dozens of functions is error-prone and tedious. By using procedural macros, we can transform a simple C-style function declaration into a high-level Rust interface at compile time.

Consider a C function: int compute_data(const char* name, double* out);.

Instead of writing the wrapper manually, we can define a procedural macro that inspects this signature and generates the boilerplate.

#[ffi_wrapper]
extern "C" {
    fn compute_data(name: *const c_char, out: *mut c_double) -> c_int;
}

The macro, using syn to parse the extern "C" block and quote to generate code, emits an idiomatic function:

pub fn compute_data(name: &str) -> Result<f64, FfiError> {
    let mut out = 0.0;
    let c_name = CString::new(name)?;
    unsafe {
        let code = raw::compute_data(c_name.as_ptr(), &mut out);
        if code == 0 { Ok(out) } else { Err(FfiError::from(code)) }
    }
}

Because this logic is injected during compilation, there is no "middle-man" overhead. The generated code is exactly what you would have written by hand, but it stays perfectly in sync with the C headers. By encoding the C API's contract—like ownership rules or nullability—into macro attributes, you shift the burden of FFI safety from the developer to the compiler. The macro effectively maps the low-level representation directly to the high-level Rust abstraction without adding a single byte of runtime code.

Key Takeaway: Procedural macros allow you to collapse the safety gap between raw C headers and idiomatic Rust by generating bespoke, zero-cost wrappers directly from your FFI declarations.