agentleFS
botirkhaltaevbotirkhaltaev/botir-skills/skills/rust-unsafe-ffi/SKILL.md

rust-unsafe-ffi

Skill4 starsChanged 3 months agoApache-2.0View on GitHub

Master unsafe Rust, FFI, and foreign function interface programming. Use when writing unsafe Rust code with raw pointers, interfacing with C/C++ libraries, creating bindings with bindgen, implementing zero-cost abstractions, working with uninitialized memory, or understanding the safety/unsafety boundary.

We split this skill into the rules it gives an agent

7 rules. 0 are common, 4 are distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 4

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Not checked for aliasing

    line 56
  2. Distinctive:

    When panic occurs, stack is unwound - must ensure no leaked resources or half-initialized data:

    line 87
  3. Very specific:

    Use std::panic::catch_unwind for fallible FFI:

    line 110
  4. Distinctive:

    Always initialize before reading:

    line 121
  5. Very specific:

    Never use std::mem::zeroed() for non-Copy types:

    line 138
  6. Very specific:

    Run cargo build to generate bindings.

    line 188
  7. Distinctive:

    When writing unsafe code:

    line 249

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Rust Unsafe & FFI Programming

Comprehensive guide to unsafe Rust and Foreign Function Interface (FFI) programming based on The Rustonomicon, Unsafe Code Guidelines, and bindgen documentation.

When to Use This Skill

  • Writing unsafe Rust code with raw pointers
  • Interfacing with C/C++ libraries via FFI
  • Creating Rust bindings to C/C++ libraries with bindgen
  • Implementing zero-cost abstractions over unsafe code
  • Working with uninitialized or untyped memory
  • Understanding the boundary between safe and unsafe Rust
  • Writing sound unsafe code that doesn't cause undefined behavior

Core References

Unsafe Rust Fundamentals

The Safety/Unsafety Boundary

Unsafe Rust exists because Rust's safety guarantees are static - they can't be satisfied for all possible programs. Unsafe code tells the compiler "I promise this is safe."

Unsafe operations (require unsafe block):

  • Dereferencing raw pointers (*const T, *mut T)
  • Calling unsafe functions or methods
  • Implementing unsafe traits
  • Accessing or modifying mutable static variables
  • Writing inline assembly

Raw Pointers

Raw pointers bypass lifetime/borrowing checks:

let num = 5;
let raw_ptr: *const i32 = #
let mut mutable = 10;
let raw_mut_ptr: *mut i32 = &mut mutable;

unsafe {
    println!("Value: {}", *raw_ptr);
    *raw_mut_ptr = 20;
}

Key differences from references:

  • Can be null
  • Not checked for aliasing
  • Can point to invalid memory
  • No lifetime tracking

Creating Safe Abstractions

Wrap unsafe code in safe APIs:

pub struct Vec<T> {
    ptr: NonNull<T>,
    len: usize,
    capacity: usize,
}

impl<T> Vec<T> {
    pub fn push(&mut self, value: T) {
        if self.len == self.capacity {
            self.reallocate();
        }
        unsafe {
            std::ptr::write(self.ptr.as_ptr().add(self.len), value);
        }
        self.len += 1;
    }
}

Exception Safety (Panic Safety)

The Panic/Unwind Safety Problem

When panic occurs, stack is unwound - must ensure no leaked resources or half-initialized data:

struct Wrapper {
    data: ManuallyDrop<String>,
}

impl Drop for Wrapper {
    fn drop(&mut self) {
        unsafe { ManuallyDrop::drop(&mut self.data) };
    }
}

Panic Safety Guarantees

LevelDescription
No guaranteeMay leak resources or corrupt data
Leak safeNo undefined behavior, but resources may leak
Unwind safeNo leaks, no corruption
Exception safeFull safety including noexcept semantics

Use std::panic::catch_unwind for fallible FFI:

std::panic::catch_unwind(|| {
    unsafe { some_ffi_call() }
})

Working with Uninitialized Memory

MaybeUninit

Always initialize before reading:

use std::mem::MaybeUninit;

let mut data: MaybeUninit<i32> = MaybeUninit::uninit();
// ... write to data ...
let value = unsafe { data.assume_init() };

For arrays of MaybeUninit:

let mut arr: [MaybeUninit<i32>; 10] = [MaybeUninit::uninit(); 10];
arr[0].write(42);

Zeroing Memory

Never use std::mem::zeroed() for non-Copy types:

use std::mem::MaybeUninit;

let mut data: MaybeUninit<String> = MaybeUninit::uninit();
// zeroed() is undefined behavior for String!

FFI (Foreign Function Interface)

Calling C Functions

use std::ffi::CString;

#[link(name = "c")]
extern "C" {
    fn printf(format: *const libc::c_char) -> libc::c_int;
}

let msg = CString::new("Hello %s\n").unwrap();
unsafe {
    printf(msg.as_ptr());
}

Using bindgen for Automatic Bindings

Add to Cargo.toml:

[build-dependencies]
bindgen = "0.69"

[dependencies]
libc = "0.2"

Create build.rs:

fn main() {
    bindgen::builder()
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file("src/bindings.rs")
        .expect("Couldn't write bindings!");
}

Run cargo build to generate bindings.

Common FFI Patterns

Passing Strings

let c_string = CString::new("rust").unwrap();
unsafe { some_c_function(c_string.as_ptr()) }

unsafe {
    let ptr = receive_c_string();
    let rust_string = CString::from_raw(ptr).into_string().unwrap();
}

Error Handling Across FFI

#[repr(C)]
pub struct FfiResult {
    pub success: bool,
    pub error_code: i32,
    pub data: *mut std::ffi::c_void,
}

Data Layout

Size, Alignment, and Padding

#[repr(C)] // C-compatible layout
struct Data {
    a: u8,   // offset 0
    _pad0: [u8; 3],
    b: u32,  // offset 4
    c: u16,  // offset 8
    _pad1: [u8; 2],
} // size = 12

#[repr(packed)] // No padding
struct Packed {
    a: u8,
    b: u32,
    c: u16,
} // size = 7

Type Punning

union IntOrFloat {
    int: u32,
    float: f32,
}

let mut value = IntOrFloat { int: 42 };
let float_val = unsafe { value.float };

Soundness Checklist

When writing unsafe code:

  • All unsafe blocks have safety comments explaining why they're safe
  • Raw pointers are never dereferenced outside unsafe blocks
  • References are never created from invalid pointers
  • Data races are prevented (use proper synchronization)
  • Memory is properly initialized before reading
  • Memory is not used after being freed
  • Panic safety is considered for allocations
  • FFI boundaries handle all error cases

Reference Map

  • references/ffi-basics.md - Raw pointers, unsafe blocks, safe abstractions
  • references/ffi-bindgen.md - Using bindgen for C/C++ bindings
  • references/memory-layout.md - Size, alignment, padding, type punning
  • references/exception-safety.md - Panic safety, unwind guarantees

Key References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.