rust-ffilisted
Install: claude install-skill rewrite-rs/skills
# Rust FFI
A boundary that is meant to stay is designed, not discovered. Moving off C —
bindgen, cbindgen, and the link into an existing build — is `/port-from-c`.
## The layer only translates
Every line of business logic lives in a normal Rust crate that knows nothing
about FFI; the `extern` layer converts types, checks pointers, and calls in.
The core crate is testable with `cargo test`, the FFI layer only through a
foreign harness — logic that drifts into the boundary is logic no test reaches.
```rust
use std::ffi::{c_char, CStr};
use std::panic::catch_unwind;
// Core: the logic, in code that knows nothing about FFI.
pub struct Client {
pub name: String,
}
impl Client {
fn new(name: &str) -> Self {
Client { name: name.to_owned() }
}
}
// Boundary: translates, checks, and calls in — nothing else.
#[repr(transparent)]
pub struct MylibClient(*mut Client);
/// # Safety
/// `name` must be NUL-terminated UTF-8, or null; the check happens once,
/// here, on the way in.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mylib_client_new(name: *const c_char) -> *mut MylibClient {
let build = || {
if name.is_null() { return None; }
let name = unsafe { CStr::from_ptr(name) }.to_str().ok()?;
Some(Box::into_raw(Box::new(Client::new(name))) as *mut MylibClient)
};
catch_unwind(build).ok().flatten().unwrap_or(std::ptr::null_mut())
}
/// # Safety
/// `client` must be a non-null handle from `mylib_client_new`, freed exactly once.