In this lesson, you will learn how to create a Rust library that acts as a curated gateway, selectively exposing underlying C functionality to other systems.
The Bridge Pattern
When working with legacy C libraries, you often face a "surface area" problem: the C library may contain hundreds of functions, global variables, and macros that are either redundant, dangerous, or confusing. Instead of forcing consumers to interact with raw extern "C" blocks, you can create a wrapper crate that acts as a facade.
By re-exporting only specific symbols, you enforce a cleaner API and provide a safer, more idiomatic surface for your users.
Implementing the Export
To re-export C symbols, you use the pub use syntax alongside your extern block. By defining your FFI layer in a private module and selectively exposing it, you maintain control over the public interface.
// In your `ffi.rs` module
mod raw {
#[link(name = "legacy_engine")]
extern "C" {
pub fn init_engine(); // Need this
pub fn process_data(); // Need this
pub fn internal_debug(); // Keep this hidden!
}
}
// In your `lib.rs`, re-export only the safe subset
pub use crate::raw::{init_engine, process_data};
Why Re-export?
- Encapsulation: You hide internal C complexity. If the underlying C library changes its function names, you only update the
ffi.rsmodule; your consumer's code remains untouched. - Namespace Control: You can re-export functions into a logical module hierarchy (e.g.,
my_crate::engine::init) rather than polluting your crate root withsnake_caseC names. - Documentation: By re-exporting, you can attach Rust documentation attributes (
///) to the exported symbols, providing help that the original C header files lack.
When re-exporting, always remember that you are essentially acting as the gatekeeper for the foreign memory. While you are merely routing the symbol, you are conceptually endorsing its safety. Ensure that you wrap these re-exported symbols in unsafe blocks only where necessary, or ideally, wrap them in safe Rust functions that handle the validation of inputs before calling the re-exported C code.
Key Takeaway: Re-exporting C symbols allows you to selectively curate a foreign API, transforming a raw, expansive C interface into a clean, well-documented, and safe Rust library.