rust-ffi

Guide agents through Rust's Foreign Function Interface: calling C from Rust with bindgen, exporting Rust to C with cbindgen, writing safe wrappers, linking libraries via build.rs , and structuring sys crates.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rust-ffi" with this command: npx skills add mohitmishra786/low-level-dev-skills/mohitmishra786-low-level-dev-skills-rust-ffi

Rust FFI

Purpose

Guide agents through Rust's Foreign Function Interface: calling C from Rust with bindgen, exporting Rust to C with cbindgen, writing safe wrappers, linking libraries via build.rs , and structuring sys crates.

Triggers

  • "How do I call a C library from Rust?"

  • "How do I use bindgen to generate Rust bindings?"

  • "How do I export Rust functions to be called from C?"

  • "How do I write a safe wrapper around an unsafe C API?"

  • "How do I link a system library in Rust?"

  • "What is a sys crate and how do I structure one?"

Workflow

  1. Calling C without bindgen (manual declarations)

// Declare external C functions manually use std::ffi::{c_int, c_char, c_void, CStr, CString};

extern "C" { fn strlen(s: *const c_char) -> usize; fn malloc(size: usize) -> *mut c_void; fn free(ptr: *mut c_void); fn my_lib_init(config: *const c_char) -> c_int; fn my_lib_process(handle: *mut c_void, data: *const u8, len: usize) -> c_int; fn my_lib_cleanup(handle: *mut c_void); }

// Call unsafe C function safely fn init(config: &str) -> Result<*mut c_void, Error> { let c_config = CString::new(config)?; let result = unsafe { my_lib_init(c_config.as_ptr()) }; if result != 0 { return Err(Error::InitFailed(result)); } // return handle... todo!() }

  1. bindgen for automatic binding generation

Cargo.toml

[build-dependencies] bindgen = "0.70"

// build.rs use std::path::PathBuf;

fn main() { println!("cargo:rerun-if-changed=wrapper.h"); println!("cargo:rustc-link-lib=mylib"); println!("cargo:rustc-link-search=/usr/local/lib");

let bindings = bindgen::Builder::default()
    .header("wrapper.h")
    .clang_arg("-I/usr/local/include")
    .clang_arg("-DMYLIB_VERSION=2")
    // Only generate bindings for this library (not system headers)
    .allowlist_function("mylib_.*")
    .allowlist_type("MyLib.*")
    .allowlist_var("MYLIB_.*")
    // Derive common traits on structs
    .derive_debug(true)
    .derive_default(true)
    // Block problematic types
    .blocklist_type("__va_list_tag")
    .generate()
    .expect("Unable to generate bindings");

let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
bindings
    .write_to_file(out_path.join("bindings.rs"))
    .expect("Couldn't write bindings!");

}

// src/lib.rs — include generated bindings #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

  1. sys crate pattern

Structure:

mylib-sys/ ├── Cargo.toml ├── build.rs # links the library ├── wrapper.h # C headers to translate └── src/ └── lib.rs # includes generated bindings

mylib/ # safe wrapper ├── Cargo.toml └── src/ └── lib.rs

mylib-sys/Cargo.toml

[package] name = "mylib-sys" version = "0.1.0" links = "mylib" # tells Cargo this crate links libmylib

[build-dependencies] bindgen = "0.70" pkg-config = "0.3" # for system library detection

// mylib-sys/build.rs fn main() { // Try pkg-config first if let Ok(lib) = pkg_config::probe_library("mylib") { for path in lib.include_paths { println!("cargo:include={}", path.display()); } return; }

// Fallback: compile from vendored source
cc::Build::new()
    .file("vendor/mylib/src/mylib.c")
    .include("vendor/mylib/include")
    .compile("mylib");

println!("cargo:rerun-if-changed=vendor/mylib/src/mylib.c");

}

  1. Writing safe wrappers

// mylib/src/lib.rs use mylib_sys as ffi; use std::ffi::{CStr, CString};

pub struct MyLib { handle: *mut ffi::mylib_t, }

// Safety: handle is not shared across threads unsafe impl Send for MyLib {} unsafe impl Sync for MyLib {}

impl MyLib { pub fn new(config: &str) -> Result<Self, Error> { let c_config = CString::new(config).map_err(|_| Error::InvalidConfig)?; let handle = unsafe { ffi::mylib_create(c_config.as_ptr()) }; if handle.is_null() { return Err(Error::InitFailed); } Ok(Self { handle }) }

pub fn process(&#x26;mut self, data: &#x26;[u8]) -> Result&#x3C;usize, Error> {
    let result = unsafe {
        ffi::mylib_process(self.handle, data.as_ptr(), data.len())
    };
    if result &#x3C; 0 {
        return Err(Error::ProcessFailed(result));
    }
    Ok(result as usize)
}

}

impl Drop for MyLib { fn drop(&mut self) { unsafe { ffi::mylib_destroy(self.handle) }; } }

  1. Exporting Rust to C with cbindgen

Cargo.toml

[build-dependencies] cbindgen = "0.27"

// src/lib.rs — exported Rust API #[no_mangle] pub extern "C" fn mylib_create(config: *const std::ffi::c_char) -> *mut MyLib { // ... Box::into_raw(Box::new(instance)) }

#[no_mangle] pub extern "C" fn mylib_destroy(ptr: *mut MyLib) { if !ptr.is_null() { unsafe { drop(Box::from_raw(ptr)) }; } }

#[no_mangle] pub extern "C" fn mylib_process( ptr: *mut MyLib, data: *const u8, len: usize, ) -> std::ffi::c_int { let lib = unsafe { &mut *ptr }; match lib.process(unsafe { std::slice::from_raw_parts(data, len) }) { Ok(n) => n as std::ffi::c_int, Err(_) => -1, } }

// build.rs fn main() { let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); cbindgen::Builder::new() .with_crate(crate_dir) .with_language(cbindgen::Language::C) .generate() .expect("Unable to generate C bindings") .write_to_file("include/mylib.h"); }

  1. Linking libraries in build.rs

// build.rs — common patterns fn main() { // Static library println!("cargo:rustc-link-lib=static=mylib"); println!("cargo:rustc-link-search=native=/path/to/lib");

// Dynamic library
println!("cargo:rustc-link-lib=dylib=mylib");

// Framework (macOS)
println!("cargo:rustc-link-lib=framework=CoreFoundation");

// Build C source with cc crate
cc::Build::new()
    .file("src/helper.c")
    .flag("-std=c11")
    .compile("helper");

}

For bindgen and cbindgen configuration details, see references/bindgen-cbindgen.md.

Related skills

  • Use skills/rust/rustc-basics for RUSTFLAGS affecting FFI builds

  • Use skills/rust/cargo-workflows for build.rs integration and sys crate layout

  • Use skills/zig/zig-cinterop for Zig's equivalent C interop approach

  • Use skills/binaries/dynamic-linking for dynamic library linking details

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cmake

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

static-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

llvm

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

gdb

No summary provided by upstream source.

Repository SourceNeeds Review