rust-ffi
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBinding Generation
绑定生成
C/C++ → Rust (bindgen)
C/C++ → Rust (bindgen)
bash
undefinedbash
undefinedAuto-generate bindings
Auto-generate bindings
bindgen input.h
--output src/bindings.rs
--allowlist-type 'my_'
--allowlist-function 'my_'
--output src/bindings.rs
--allowlist-type 'my_'
--allowlist-function 'my_'
undefinedbindgen input.h
--output src/bindings.rs
--allowlist-type 'my_'
--allowlist-function 'my_'
--output src/bindings.rs
--allowlist-type 'my_'
--allowlist-function 'my_'
undefinedRust → C (cbindgen)
Rust → C (cbindgen)
bash
undefinedbash
undefinedGenerate C header
Generate C header
cbindgen --crate mylib --output include/mylib.h
undefinedcbindgen --crate mylib --output include/mylib.h
undefinedSolution Patterns
解决方案模式
Pattern 1: Calling C Functions
模式1:调用C函数
rust
use std::ffi::{CStr, CString};
use libc::c_int;
#[link(name = "curl")]
extern "C" {
fn curl_version() -> *const libc::c_char;
fn curl_easy_perform(curl: *mut c_int) -> c_int;
}
// ✅ Safe wrapper
fn get_version() -> String {
unsafe {
let ptr = curl_version();
// SAFETY: curl_version returns valid null-terminated string
CStr::from_ptr(ptr).to_string_lossy().into_owned()
}
}rust
use std::ffi::{CStr, CString};
use libc::c_int;
#[link(name = "curl")]
extern "C" {
fn curl_version() -> *const libc::c_char;
fn curl_easy_perform(curl: *mut c_int) -> c_int;
}
// ✅ Safe wrapper
fn get_version() -> String {
unsafe {
let ptr = curl_version();
// SAFETY: curl_version returns valid null-terminated string
CStr::from_ptr(ptr).to_string_lossy().into_owned()
}
}Pattern 2: String Passing
模式2:字符串传递
rust
// ✅ Safe way to pass strings
fn process_c_string(s: &CStr) {
// SAFETY: s is a valid CStr, ptr is valid for call duration
unsafe {
some_c_function(s.as_ptr());
}
}
// Creating CString from Rust
fn get_c_string() -> Result<CString, std::ffi::NulError> {
CString::new("hello")
}
// ❌ Dangerous: temporary CString
// let ptr = CString::new("hello").unwrap().as_ptr(); // Dangling!
// ✅ Correct: keep CString alive
let c_str = CString::new("hello")?;
let ptr = c_str.as_ptr();
// use ptr...
// c_str dropped hererust
// ✅ Safe way to pass strings
fn process_c_string(s: &CStr) {
// SAFETY: s is a valid CStr, ptr is valid for call duration
unsafe {
some_c_function(s.as_ptr());
}
}
// Creating CString from Rust
fn get_c_string() -> Result<CString, std::ffi::NulError> {
CString::new("hello")
}
// ❌ Dangerous: temporary CString
// let ptr = CString::new("hello").unwrap().as_ptr(); // Dangling!
// ✅ Correct: keep CString alive
let c_str = CString::new("hello")?;
let ptr = c_str.as_ptr();
// use ptr...
// c_str dropped herePattern 3: Callback Functions
模式3:回调函数
rust
extern "C" fn callback(data: *mut libc::c_void) {
// SAFETY: data must be a valid pointer to UserData
// Caller guarantees this invariant
unsafe {
let user_data: &mut UserData = &mut *(data as *mut UserData);
user_data.count += 1;
}
}
fn register_callback(callback: extern "C" fn(*mut c_void), data: *mut c_void) {
unsafe {
some_c_lib_register(callback, data);
}
}rust
extern "C" fn callback(data: *mut libc::c_void) {
// SAFETY: data must be a valid pointer to UserData
// Caller guarantees this invariant
unsafe {
let user_data: &mut UserData = &mut *(data as *mut UserData);
user_data.count += 1;
}
}
fn register_callback(callback: extern "C" fn(*mut c_void), data: *mut c_void) {
unsafe {
some_c_lib_register(callback, data);
}
}Pattern 4: C++ Interop with cxx
模式4:使用cxx实现C++互操作
rust
// Using cxx for safe C++ FFI
use cxx::CxxString;
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("my_library.h");
type MyClass;
fn do_something(&self, input: i32) -> i32;
fn get_data(&self) -> &CxxString;
}
}
struct RustWrapper {
inner: cxx::UniquePtr<ffi::MyClass>,
}
impl RustWrapper {
pub fn new() -> Self {
Self {
inner: ffi::create_my_class(),
}
}
pub fn do_something(&self, input: i32) -> i32 {
self.inner.do_something(input)
}
}rust
// Using cxx for safe C++ FFI
use cxx::CxxString;
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("my_library.h");
type MyClass;
fn do_something(&self, input: i32) -> i32;
fn get_data(&self) -> &CxxString;
}
}
struct RustWrapper {
inner: cxx::UniquePtr<ffi::MyClass>,
}
impl RustWrapper {
pub fn new() -> Self {
Self {
inner: ffi::create_my_class(),
}
}
pub fn do_something(&self, input: i32) -> i32 {
self.inner.do_something(input)
}
}Data Type Mapping
数据类型映射
| Rust | C | Notes |
|---|---|---|
| | Usually matches |
| | Platform-dependent |
| | Pointer-sized |
| | Read-only |
| | Mutable |
| | UTF-8 guaranteed |
| | Ownership transfer |
| | Non-null pointer |
| | Nullable pointer |
| Rust | C | 说明 |
|---|---|---|
| | 通常匹配 |
| | 依赖平台 |
| | 指针大小 |
| | 只读 |
| | 可变 |
| | 保证UTF-8编码 |
| | 所有权转移 |
| | 非空指针 |
| | 可空指针 |
Error Handling
错误处理
C Error Codes
C错误码
rust
fn call_c_api() -> Result<(), Box<dyn std::error::Error>> {
// SAFETY: c_function is properly initialized
let result = unsafe { c_function_that_returns_int() };
if result < 0 {
return Err(format!("C API error: {}", result).into());
}
Ok(())
}rust
fn call_c_api() -> Result<(), Box<dyn std::error::Error>> {
// SAFETY: c_function is properly initialized
let result = unsafe { c_function_that_returns_int() };
if result < 0 {
return Err(format!("C API error: {}", result).into());
}
Ok(())
}Panic Across FFI
FFI边界的恐慌处理
rust
// Panics across FFI boundary = UB
// Must catch or prevent
#[no_mangle]
pub extern "C" fn safe_call() -> i32 {
let result = std::panic::catch_unwind(|| {
rust_code_that_might_panic()
});
match result {
Ok(value) => value,
Err(_) => -1, // Error code
}
}rust
// Panics across FFI boundary = UB
// Must catch or prevent
#[no_mangle]
pub extern "C" fn safe_call() -> i32 {
let result = std::panic::catch_unwind(|| {
rust_code_that_might_panic()
});
match result {
Ok(value) => value,
Err(_) => -1, // Error code
}
}C++ Exceptions
C++异常处理
rust
// C++ exceptions → Rust panic (with cxx)
// Must catch at FFI boundary
#[no_mangle]
pub extern "C" fn safe_cpp_call(error_code: *mut i32) -> *const c_char {
let result = std::panic::catch_unwind(|| {
unsafe { cpp_function() }
});
match result {
Ok(Ok(value)) => value.as_ptr(),
Ok(Err(e)) => {
if !error_code.is_null() {
unsafe { *error_code = e.code(); }
}
std::ptr::null()
}
Err(_) => {
if !error_code.is_null() {
unsafe { *error_code = -999; }
}
std::ptr::null()
}
}
}rust
// C++ exceptions → Rust panic (with cxx)
// Must catch at FFI boundary
#[no_mangle]
pub extern "C" fn safe_cpp_call(error_code: *mut i32) -> *const c_char {
let result = std::panic::catch_unwind(|| {
unsafe { cpp_function() }
});
match result {
Ok(Ok(value)) => value.as_ptr(),
Ok(Err(e)) => {
if !error_code.is_null() {
unsafe { *error_code = e.code(); }
}
std::ptr::null()
}
Err(_) => {
if !error_code.is_null() {
unsafe { *error_code = -999; }
}
std::ptr::null()
}
}
}Memory Management
内存管理
| Scenario | Who Frees | How |
|---|---|---|
| C allocates, Rust uses | C | Don't free from Rust |
| Rust allocates, C uses | Rust | C notifies when done |
| Shared buffer | Agreed protocol | Document clearly |
rust
// ✅ Rust allocates, C borrows
#[no_mangle]
pub extern "C" fn create_buffer(len: usize) -> *mut u8 {
let mut buf = vec![0u8; len];
let ptr = buf.as_mut_ptr();
std::mem::forget(buf); // Don't drop
ptr
}
#[no_mangle]
pub extern "C" fn free_buffer(ptr: *mut u8, len: usize) {
unsafe {
// SAFETY: ptr was allocated by create_buffer with this len
let _ = Vec::from_raw_parts(ptr, len, len);
} // Vec dropped, memory freed
}| 场景 | 谁负责释放 | 方式 |
|---|---|---|
| C分配,Rust使用 | C | 不要在Rust中释放 |
| Rust分配,C使用 | Rust | C使用完成后通知Rust |
| 共享缓冲区 | 约定协议 | 明确文档说明 |
rust
// ✅ Rust allocates, C borrows
#[no_mangle]
pub extern "C" fn create_buffer(len: usize) -> *mut u8 {
let mut buf = vec![0u8; len];
let ptr = buf.as_mut_ptr();
std::mem::forget(buf); // Don't drop
ptr
}
#[no_mangle]
pub extern "C" fn free_buffer(ptr: *mut u8, len: usize) {
unsafe {
// SAFETY: ptr was allocated by create_buffer with this len
let _ = Vec::from_raw_parts(ptr, len, len);
} // Vec dropped, memory freed
}Workflow
工作流程
Step 1: Choose FFI Strategy
步骤1:选择FFI策略
Need to call C code?
→ Simple functions? Manual extern declarations
→ Complex API? Use bindgen
→ C++? Use cxx crate
Exporting to C?
→ Use cbindgen to generate headers
→ Mark functions #[no_mangle]
→ Use extern "C"需要调用C代码?
→ 简单函数?手动声明extern
→ 复杂API?使用bindgen
→ C++?使用cxx crate
导出到C?
→ 使用cbindgen生成头文件
→ 为函数标记#[no_mangle]
→ 使用extern "C"Step 2: Define Safety Invariants
步骤2:定义安全不变量
For every FFI call:
1. Document pointer validity requirements
2. Document lifetime expectations
3. Document thread safety assumptions
4. Document panic handling对于每个FFI调用:
1. 记录指针有效性要求
2. 记录生命周期预期
3. 记录线程安全假设
4. 记录恐慌处理方式Step 3: Build Safe Wrapper
步骤3:构建安全封装
unsafe FFI calls
↓
Safe private functions (validate inputs)
↓
Safe public API (no unsafe visible)不安全的FFI调用
↓
安全的私有函数(验证输入)
↓
安全的公开API(无可见unsafe代码)Step 4: Test Thoroughly
步骤4:全面测试
bash
undefinedbash
undefinedTest with Miri
Test with Miri
cargo +nightly miri test
cargo +nightly miri test
Memory safety check
Memory safety check
valgrind ./target/release/program
valgrind ./target/release/program
Cross-compile test
Cross-compile test
cargo build --target x86_64-unknown-linux-gnu
undefinedcargo build --target x86_64-unknown-linux-gnu
undefinedLanguage-Specific Tools
特定语言工具
| Language | Tool | Use Case |
|---|---|---|
| Python | PyO3 | Python extensions |
| Java | jni | Android/JVM |
| Node.js | napi-rs | Node.js addons |
| C# | csharp-bindgen | .NET interop |
| Go | cgo | Go bridge |
| C++ | cxx | Safe C++ FFI |
| 语言 | 工具 | 适用场景 |
|---|---|---|
| Python | PyO3 | Python扩展 |
| Java | jni | Android/JVM |
| Node.js | napi-rs | Node.js插件 |
| C# | csharp-bindgen | .NET互操作 |
| Go | cgo | Go桥接 |
| C++ | cxx | 安全C++ FFI |
Common Pitfalls
常见陷阱
| Pitfall | Consequence | Avoid By |
|---|---|---|
| String encoding error | Garbled text | Use CStr/CString |
| Lifetime mismatch | Use-after-free | Clear ownership |
| Cross-thread non-Send | Data race | Arc + Mutex |
| Fat pointer to C | Memory corruption | Flatten data |
| Missing #[no_mangle] | Symbol not found | Explicit export |
| Panic across FFI | UB | catch_unwind |
| 陷阱 | 后果 | 避免方式 |
|---|---|---|
| 字符串编码错误 | 文本乱码 | 使用CStr/CString |
| 生命周期不匹配 | 释放后使用 | 明确所有权 |
| 跨线程非Send | 数据竞争 | 使用Arc + Mutex |
| 胖指针传递给C | 内存损坏 | 扁平化数据 |
| 缺少#[no_mangle] | 符号未找到 | 显式导出 |
| FFI边界恐慌 | 未定义行为 | 使用catch_unwind |
Review Checklist
审查检查清单
When reviewing FFI code:
- All extern functions have SAFETY comments
- String conversion uses CStr/CString properly
- Memory ownership is clearly documented
- No panics across FFI boundary (use catch_unwind)
- FFI types use #[repr(C)]
- Raw pointers validated before dereferencing
- Functions exported with #[no_mangle]
- Callbacks have correct ABI (extern "C")
- Tested with Miri for UB detection
- Documentation explains ownership protocol
审查FFI代码时:
- 所有extern函数都有SAFETY注释
- 字符串转换正确使用CStr/CString
- 内存所有权有明确文档说明
- FFI边界无恐慌(使用catch_unwind)
- FFI类型使用#[repr(C)]
- 原始指针在解引用前已验证
- 导出函数标记了#[no_mangle]
- 回调函数使用正确的ABI(extern "C")
- 使用Miri测试检测未定义行为
- 文档说明所有权协议
Verification Commands
验证命令
bash
undefinedbash
undefinedCheck safety
Check safety
cargo +nightly miri test
cargo +nightly miri test
Memory leaks
Memory leaks
valgrind --leak-check=full ./target/release/program
valgrind --leak-check=full ./target/release/program
Generate bindings
Generate bindings
bindgen wrapper.h --output src/ffi.rs
bindgen wrapper.h --output src/ffi.rs
Generate C header
Generate C header
cbindgen --lang c --output target/mylib.h
cbindgen --lang c --output target/mylib.h
Check exports
Check exports
nm target/release/libmylib.so | grep my_function
undefinednm target/release/libmylib.so | grep my_function
undefinedSafety Guidelines
安全指南
- Minimize unsafe: Only wrap necessary C calls
- Defensive programming: Check null pointers, validate ranges
- Clear documentation: Who owns memory, who frees it
- Test coverage: FFI bugs are extremely hard to debug
- Use Miri: Detect undefined behavior early
- 最小化unsafe代码:仅封装必要的C调用
- 防御式编程:检查空指针,验证范围
- 清晰的文档:明确谁拥有内存、谁负责释放
- 测试覆盖:FFI错误极难调试
- 使用Miri:尽早检测未定义行为
Related Skills
相关技能
- rust-unsafe - Unsafe code fundamentals
- rust-ownership - Memory and lifetime management
- rust-coding - Export conventions
- rust-performance - FFI overhead optimization
- rust-web - Using FFI in web services
- rust-unsafe - 不安全代码基础
- rust-ownership - 内存与生命周期管理
- rust-coding - 导出约定
- rust-performance - FFI开销优化
- rust-web - 在Web服务中使用FFI
Localized Reference
本地化参考
- Chinese version: SKILL_ZH.md - 完整中文版本,包含所有内容
- Chinese version: SKILL_ZH.md - 完整中文版本,包含所有内容