FFI and Calling C from Rust
TL;DR: This guide covers FFI (Foreign Function Interface) in Rust. You’ll learn to call C libraries, use bindgen for code generation, and handle unsafe boundaries safely.
Introduction
FFI allows Rust to interoperate with C libraries. Use cases include:
- Using existing C libraries
- Performance-critical code
- System-level programming
- Hardware access
Basic FFI
Calling C Functions
use std::ffi::CString;
#[link(name = "crypto")]
extern "C" {
fn md5(data: *const u8, len: usize) -> *mut u8;
}
pub fn compute_md5(input: &[u8]) -> Vec<u8> {
unsafe {
let ptr = md5(input.as_ptr(), input.len());
let result = std::slice::from_raw_parts(ptr, 16).to_vec();
libc::free(ptr as *mut libc::c_void);
result
}
}
Using Bindgen
Automatic Bindings
// build.rs
fn main() {
println!("cargo:rerun-if-changed=bindings.h");
bindgen::builder()
.header("bindings.h")
.generate()
.expect("Unable to generate bindings")
.write_to_file("src/bindings.rs")
.expect("Couldn't write bindings!");
}
Safe Wrappers
pub struct CLibrary {
handle: *mut libc::c_void,
}
impl CLibrary {
pub fn new() -> Result<Self, Error> {
let handle = unsafe { libc_init() };
if handle.is_null() {
Err(Error::InitFailed)
} else {
Ok(Self { handle })
}
}
pub fn process(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
if self.handle.is_null() {
return Err(Error::NotInitialized);
}
unsafe {
let output = libc_process(self.handle, data.as_ptr(), data.len());
// Convert output to Vec
Ok(result)
}
}
}
Conclusion
FFI enables Rust to use C libraries:
- extern “C” - Declare external functions
- bindgen - Auto-generate bindings
- Safe wrappers - Encapsulate unsafe code
- Memory safety - Proper ownership
Comments