/rust-ffi
Rust FFI skill for C interoperability. Use when calling C libraries from Rust, generating Rust bindings with bindgen, exporting Rust functions to C with cbindgen, writing safe wrappers around unsafe FFI, or linking system and vendor libraries. Activates on queries about bindgen,
$ npx -y skills add mohitmishra786/low-level-dev-skills --skill rust-ffi --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rust-ffi
Context preview
The summary Claude sees to decide when to auto-load this skill.
Rust FFI skill for C interoperability. Use when calling C libraries from Rust, generating Rust bindings with bindgen, exporting Rust functions to C with cbindgen, writing safe wrappers around unsafe FFI, or linking system and vendor libraries. Activates on queries about bindgen,
SKILL.md
rust-ffi.SKILL.mdname: rust-ffi
description: Rust FFI skill for C interoperability. Use when calling C libraries from Rust, generating Rust bindings with bindgen, exporting Rust functions to C with cbindgen, writing safe wrappers around unsafe FFI, or linking system and vendor libraries. Activates on queries about bindgen, cbindgen, extern "C", unsafe FFI, Rust C bindings, linking C from Rust, or sys crates.
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!()
}2. 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"));3. 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");
}4. 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(&mut self, data: &[u8]) -> Result<usize, Error> {
let result = unsafe {
ffi::mylib_process(self.handle, data.as_ptr(), data.len())
};
if result < 0 {
return Err(Error::ProcessFailed(result));
}
Ok(result as usize)
}
}
impl Drop for MyLib {
fn drop(&mut self) {
unsafe { ffi::mylib_destroy(self.handle) };
}
}5. 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_craRead more
name: rust-ffi description: Rust FFI skill for C interoperability. Use when calling C libraries from Rust, generating Rust bindings with bindgen, exporting Rust functions to C with cbindgen, writing safe wrappers around unsafe FFI, or linking system and vendor libraries. Activates on queries about bindgen, cbindgen, extern "C", unsafe FFI, Rust C bindings, linking C from Rust, or sys crates.
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!()
}2. 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"));3. 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");
}4. 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(&mut self, data: &[u8]) -> Result<usize, Error> {
let result = unsafe {
ffi::mylib_process(self.handle, data.as_ptr(), data.len())
};
if result < 0 {
return Err(Error::ProcessFailed(result));
}
Ok(result as usize)
}
}
impl Drop for MyLib {
fn drop(&mut self) {
unsafe { ffi::mylib_destroy(self.handle) };
}
}5. 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_craA curated suite of AI agent skills for systems and low-level programming — C/C++, Rust, Zig, GPU, bare-metal firmware, Linux kernel/driver development, computer architecture, compiler internals, HPC, and more.
Repo: mohitmishra786/low-level-dev-skills
Other skills on low-level-dev-skills.
- /custom-allocators
Custom allocator skill for memory allocation strategies. Use when implementing pool/slab/arena allocators, tuning jemalloc/mimalloc, writing Rust GlobalAlloc, or benchmarking allocator performance. Activates on queries about jemalloc, mimalloc, tcmalloc, arena allocator,
Open skill - /numa-programming
NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology,
Open skill - /af-xdp
AF_XDP skill for high-performance XDP sockets. Use when creating AF_XDP sockets, configuring UMEM and XSK rings, XDP_REDIRECT programs, copy vs zero-copy mode, or comparing with DPDK. Activates on queries about AF_XDP, xsk_umem, XDP_REDIRECT, libbpf xsk, or zero-copy XDP.
Open skill - /dpdk
DPDK skill for userspace packet I/O. Use when initializing EAL, configuring PMD drivers, using mbuf pools and rte_ring, setting up huge pages, RSS, or testpmd validation. Activates on queries about DPDK, EAL, rte_eth_rx_burst, hugepages, PMD, or testpmd.
Open skill - /io-uring
io_uring skill for Linux async I/O. Use when building high-performance servers with liburing, multi-shot operations, provided buffers, fixed files, zero-copy send, or tokio-uring. Activates on queries about io_uring, SQE/CQE, liburing, IORING_OP_PROVIDE_BUFFERS, or io_uring vs
Open skill - /adc-dac-baremetal
Bare-metal ADC and DAC skill. Use when configuring analog sampling, DMA-driven ADC, calibration, or DAC output on MCUs. Activates on queries about ADC bare-metal, sampling time, DMA ADC, or DAC channel setup.
Open skill

