luckyegg168/rust-skill

rust-ffi-interop

Rust FFI 互操作技能。涵蓋 PyO3 0.23 Rust ↔ Python 互操作、 cbindgen 生成 C header、bindgen 綁定 C 函式庫、extern "C" ABI、 [repr(C)] 記憶體佈局、CString/CStr 安全轉換、 null pointer 處理、panic boundary(catchunwind)、 記憶體所有權跨語言邊界管理、WASM 目標編譯。 觸發關鍵詞:FFI, PyO3, cbindgen, bindgen, extern C, repr…

查看源码

使用方法

  1. 复制安装命令,在自己的智能体环境安装前先检查源码包。
  2. 从原始文档核对所需工具、凭据和支持的宿主。
  3. 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/luckyegg168/rust-skill --skill rust-ffi-interop

开始前需了解

  • 安装不包含模型订阅、API 额度或访问文件的权限。
  • 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
仓库原始内容

按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。

Rust FFI 互操作(Foreign Function Interface)

適用場景

  • 需要從 Python 呼叫 Rust 高效能邏輯(PyO3)
  • 需要將 Rust 函式庫封裝為 C ABI 供其他語言呼叫(cbindgen)
  • 需要在 Rust 中呼叫既有 C/C++ 函式庫(bindgen)
  • 需要產出 .so / .dll / .dylib 動態函式庫
  • 跨語言邊界的記憶體安全設計與 panic 隔離
  • 將 Rust 編譯為 WASM 供瀏覽器或 Node.js 使用

核心知識

FFI 策略選擇表

需求工具crate-type說明
Rust → PythonPyO3 + maturincdylib產出 .pyd/.so,pip install 即用
Rust → C headercbindgencdylib/staticlib自動從 Rust 原始碼產生 .h
C lib → Rustbindgen-從 .h 自動產生 Rust FFI 繫結
Rust → WASMwasm-bindgencdylib搭配 wasm-pack 打包
手動 FFIextern "C"cdylib/staticlib最底層控制

PyO3 基礎流程

  1. Cargo.toml 加入 pyo3 並設定 crate-type = ["cdylib"]
  2. 使用 #[pyfunction]、#[pyclass]、#[pymethods] 標記
  3. 以 #[pymodule] 組裝模組
  4. 用 maturin develop 本機安裝或 maturin build --release 產出 wheel
  5. Python 端直接 import 使用

PyO3 0.23 重點變更:

  • Bound<'py, T> 取代舊版 &PyAny,生命週期更明確
  • #[pymodule] 可搭配宣告式巨集 #[pymodule_init]
  • 錯誤轉換透過 PyErr::new::<PyValueError, _>(msg) 或實作 From<E> for PyErr

cbindgen / bindgen 工具鏈

cbindgen(Rust → C header):

toml
# cbindgen.toml
language = "C"
include_guard = "MY_LIB_H"
no_includes = true

執行 cbindgen --config cbindgen.toml --crate my_lib --output my_lib.h

bindgen(C header → Rust):

rust
// build.rs
fn main() {
    println!("cargo:rustc-link-lib=foo");
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect("無法產生繫結");
    let out = std::env::var("OUT_DIR").unwrap();
    bindings.write_to_file(std::path::PathBuf::from(out).join("bindings.rs"))
        .expect("寫入繫結失敗");
}

#[repr(C)] 與記憶體佈局

rust
// 確保欄位順序與 C struct 一致,無填充重排
#[repr(C)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

關鍵規則:

  • FFI struct 必須 標註 #[repr(C)],否則 Rust 編譯器可能重排欄位
  • 使用 #[repr(C, packed)] 可移除所有 padding(需小心對齊問題)
  • 列舉跨 FFI 應使用 #[repr(i32)] 或 #[repr(u8)] 明確指定底層型別
  • bool 在 FFI 中應改用 i32 或 u8,因為 C _Bool 與 Rust bool 語意微妙差異

跨語言所有權規則

配置方釋放方策略
RustRust提供 free_*() 函式,由外部語言呼叫
CCRust 只借用指標,不釋放
Rust (Box)外部Box::into_raw() 轉移;回收時 Box::from_raw()
C (malloc)外部Rust 用完後呼叫 C 的 free()

CString / CStr 安全轉換:

rust
// Rust String → C 字串(配置在 Rust 端)
let c_string = std::ffi::CString::new("hello").expect("含有 null byte");
let ptr = c_string.into_raw(); // 所有權轉給外部
// 回收
unsafe { let _ = CString::from_raw(ptr); }

// C 字串 → Rust(借用,不轉移所有權)
unsafe {
    let c_str = std::ffi::CStr::from_ptr(ptr);
    let rust_str = c_str.to_str().expect("無效 UTF-8");
}

Panic Safety(catch_unwind)

Rust panic 跨 FFI 邊界是未定義行為。所有 extern "C" 函式必須攔截 panic:

rust
use std::panic::catch_unwind;

#[no_mangle]
pub extern "C" fn safe_entry(x: i32) -> i32 {
    match catch_unwind(|| {
        // 可能 panic 的邏輯
        if x < 0 { panic!("負數輸入"); }
        x * 2
    }) {
        Ok(val) => val,
        Err(_) => -1, // 錯誤哨兵值
    }
}

注意: catch_unwind 只能攔截 panic!(unwind),無法攔截 abort。 設定 panic = "abort" 時 catch_unwind 無效。

程式碼範例

Basic:extern "C" 函式 + CString + #[repr(C)]

rust
// 見 examples/basic.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

/// C 相容的二維點結構
#[repr(C)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

/// 計算兩點之間的歐幾里得距離
#[no_mangle]
pub extern "C" fn point_distance(a: &Point, b: &Point) -> f64 {
    ((a.x - b.x).powi(2) + (a.y - b.y).powi(2)).sqrt()
}

/// 從 C 字串建立問候訊息,回傳新配置的 C 字串
/// 呼叫方必須以 `greeting_free` 釋放回傳值
#[no_mangle]
pub extern "C" fn greeting_new(name: *const c_char) -> *mut c_char {
    if name.is_null() {
        return std::ptr::null_mut();
    }
    let c_str = unsafe { CStr::from_ptr(name) };
    let name_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return std::ptr::null_mut(),
    };
    let msg = format!("你好, {}!", name_str);
    match CString::new(msg) {
        Ok(cs) => cs.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// 釋放由 `greeting_new` 配置的 C 字串
#[no_mangle]
pub extern "C" fn greeting_free(ptr: *mut c_char) {
    if !ptr.is_null() {
        unsafe { let _ = CString::from_raw(ptr); }
    }
}

Intermediate:PyO3 Python Module

rust
// 見 examples/intermediate.rs
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;

/// 二維向量,可從 Python 直接建構與操作
#[pyclass]
#[derive(Clone)]
struct Vec2 {
    #[pyo3(get, set)]
    x: f64,
    #[pyo3(get, set)]
    y: f64,
}

#[pymethods]
impl Vec2 {
    #[new]
    fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    /// 向量長度
    fn magnitude(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }

    /// 回傳單位向量;零向量時拋出 ValueError
    fn normalize(&self) -> PyResult<Self> {
        let mag = self.magnitude();
        if mag < f64::EPSILON {
            return Err(PyValueError::new_err("無法正規化零向量"));
        }
        Ok(Self { x: self.x / mag, y: self.y / mag })
    }

    fn __repr__(&self) -> String {
        format!("Vec2(x={}, y={})", self.x, self.y)
    }
}

/// 批次計算點對之間的距離
#[pyfunction]
fn batch_distances(points: Vec<(f64, f64)>) -> PyResult<Vec<f64>> {
    if points.len() % 2 != 0 {
        return Err(PyValueError::new_err("點數必須為偶數"));
    }
    let dists = points.chunks(2).map(|pair| {
        let (x1, y1) = pair[0];
        let (x2, y2) = pair[1];
        ((x1 - x2).powi(2) + (y1 - y2).powi(2)).sqrt()
    }).collect();
    Ok(dists)
}

/// Python 模組進入點
#[pymodule]
fn my_geometry(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<Vec2>()?;
    m.add_function(wrap_pyfunction!(batch_distances, m)?)?;
    Ok(())
}

Advanced:bindgen + cbindgen + catch_unwind

rust
// 見 examples/advanced.rs — bindgen 繫結 + 安全包裝 + panic 邊界

// --- build.rs(bindgen 從 C header 產生繫結)---
// fn main() {
//     println!("cargo:rustc-link-lib=zlib");
//     let bindings = bindgen::Builder::default()
//         .header("wrapper.h")  // #include <zlib.h>
//         .allowlist_function("compress|uncompress|zlibVersion")
//         .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
//         .generate().expect("bindgen 失敗");
//     let out = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
//     bindings.write_to_file(out.join("zlib_bindings.rs")).unwrap();
// }

// --- 安全包裝層 ---
mod zlib_safe {
    #![allow(non_upper_case_globals, non_camel_case_types)]
    include!(concat!(env!("OUT_DIR"), "/zlib_bindings.rs"));

    use std::ptr;

    /// 對 zlib compress 的安全封裝
    pub fn compress_data(input: &[u8]) -> Result<Vec<u8>, String> {
        let mut dest_len = (input.len() + 128) as libc::c_ulong;
        let mut dest = vec![0u8; dest_len as usize];
        let ret = unsafe {
            compress(
                dest.as_mut_ptr(),
                &mut dest_len as *mut _,
                input.as_ptr(),
                input.len() as libc::c_ulong,
            )
        };
        if ret != 0 {
            return Err(format!("壓縮失敗,錯誤碼: {}", ret));
        }
        dest.truncate(dest_len as usize);
        Ok(dest)
    }
}

// --- cbindgen 匯出層(帶 catch_unwind)---
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_uchar};
use std::panic::catch_unwind;
use std::slice;

/// 回傳壓縮後的資料;成功回傳 0,失敗回傳 -1
/// `out_len` 會被寫入實際輸出長度
#[no_mangle]
pub extern "C" fn mylib_compress(
    input: *const c_uchar,
    input_len: usize,
    output: *mut c_uchar,
    output_cap: usize,
    out_len: *mut usize,
) -> c_int {
    // 攔截所有 panic,防止跨 FFI 傳播
    let result = catch_unwind(|| {
        // 空指標檢查
        if input.is_null() || output.is_null() || out_len.is_null() {
            return -1;
        }
        let in_slice = unsafe { slice::from_raw_parts(input, input_len) };
        match zlib_safe::compress_data(in_slice) {
            Ok(compressed) => {
                if compressed.len() > output_cap {
                    return -2; // 緩衝區不足
                }
                unsafe {
                    ptr::copy_nonoverlapping(
                        compressed.as_ptr(), output, compressed.len()
                    );
                    *out_len = compressed.len();
                }
                0
            }
            Err(_) => -1,
        }
    });
    result.unwrap_or(-99) // panic 時回傳 -99
}

/// 釋放由本函式庫配置的字串
#[no_mangle]
pub extern "C" fn mylib_free_string(ptr: *mut c_char) {
    if !ptr.is_null() {
        unsafe { let _ = CString::from_raw(ptr); }
    }
}

常見錯誤對照表

錯誤訊息 / 症狀原因修復方式
undefined symbol: my_func缺少 #[no_mangle] 或 extern "C",或 crate-type 未設定 cdylib確認 #[no_mangle] pub extern "C",Cargo.toml 含 crate-type = ["cdylib"]
Segfault(呼叫時崩潰)傳入 null 指標未檢查,或 CStr::from_ptr 收到非法指標進入函式後立即檢查 ptr.is_null(),不信任外部指標有效性
panic across FFI boundary is UBRust panic 未被攔截就跨越 extern "C" 邊界所有 FFI 入口包裹 std::panic::catch_unwind,回傳錯誤碼
記憶體洩漏(Valgrind 報告)CString::into_raw() 後未提供對應的 free 函式匯出配對的 *_free() 函式,文件中明確標示呼叫方責任
expected struct X, found struct X(型別不匹配)C 端 struct 佈局與 Rust 不一致,缺少 #[repr(C)]FFI struct 必須加 #[repr(C)];用 cbindgen 自動產生 header 確保一致
thread 'main' panicked: interior nul byteCString::new() 的輸入含 \0先過濾或以 CString::new(s) 的 Err 分支處理
PyO3: PyErr { type: TypeError }Python 傳入型別與 Rust 函式簽名不匹配使用 PyAny + 手動 extract::<T>() 並回傳明確錯誤訊息
bindgen: fatal error: header not foundwrapper.h 路徑錯誤或系統缺乏 C library 的 -dev 套件安裝開發套件(如 apt install libz-dev),確認 build.rs 路徑

Cargo.toml 依賴模板

toml
[package]
name = "my_ffi_lib"
version = "0.1.0"
edition = "2024"

[lib]
# cdylib: 動態函式庫(.so/.dll/.dylib),供 C/Python/其他語言載入
# staticlib: 靜態函式庫(.a),鏈結進 C/C++ 專案
crate-type = ["cdylib", "staticlib"]

[dependencies]
# PyO3:Rust ↔ Python 互操作框架
pyo3 = { version = "0.23", features = ["extension-module"] }
# libc:C 標準型別定義
libc = "0.2"

[build-dependencies]
# bindgen:從 C header 自動產生 Rust FFI 繫結
bindgen = "0.71"
# cbindgen:從 Rust 原始碼自動產生 C header
cbindgen = "0.27"

# --- 單獨使用時可選擇性啟用 ---
# [features]
# python = ["pyo3/extension-module"]
# c-bindings = []

maturin 搭配 PyO3 的 `pyproject.toml`:

toml
[build-system]
requires = ["maturin>=1.7,<2"]
build-backend = "maturin"

[project]
name = "my_geometry"
requires-python = ">=3.9"

[tool.maturin]
features = ["pyo3/extension-module"]

參考來源

#來源說明
1The Rustonomicon — FFI官方 FFI 不安全操作完整指南
2PyO3 User GuidePyO3 0.23 官方文件
3cbindgen User GuideMozilla 維護的 C header 產生器文件
4bindgen User Guide官方 C → Rust 繫結產生器指南
5Rust Reference — Type Layoutrepr(C) 與記憶體佈局官方規格
6maturin DocumentationPyO3 建置工具 maturin 完整文件

詳細來源清單見 references/sources.md。

来自同一仓库

更多 Skills

全部 Skills
luckyegg168
社区

rust-async-runtime

Rust 非同步執行時技能。涵蓋 tokio 1.44 runtime 架構與選擇、async/await 語法模式、 Future trait 原理、常見陷阱(blocking in async、async trait)、 channel 通訊(mpsc/oneshot/broadcast/watch)、task spawn 策略、 select! 多路複用、graceful shutdown、async stream、Mutex 在 async 中的正確用法。 觸發關鍵詞:async, await, tokio, async-std, Future, spawn, channel, mpsc, select, runtime, blocking, async trait, stream, executor

安装量
1
GitHub Stars
0
最近更新
3月29日
luckyegg168
社区

rust-crate-selection

Rust crate 選擇指南技能。按功能領域推薦經過實戰驗證的 crate: HTTP(reqwest/hyper/axum)、序列化(serde/serdejson/toml)、 CLI(clap/dialoguer/indicatif)、日誌(tracing/log/envlogger)、 加密(ring/rustls/argon2)、資料庫(sqlx/diesel/sea-orm)、 GUI(egui/iced/tauri)、測試(proptest/mockall/criterion)、 非同步(tokio/async-trait/futures)。 含選擇判斷標準、版本鎖定策略、Cargo.toml features 管理。 觸發關鍵詞:Rust crate, which crate, recommend crate, crate comparison, best Rust library, cargo dependency, version pinning, blessed.rs, lib.rs, crates.io, feature flags

安装量
1
GitHub Stars
0
最近更新
3月29日
luckyegg168
社区

rust-design-patterns

Rust 慣用設計模式技能。涵蓋 Builder Pattern(型別安全構建器)、 Newtype Pattern(型別封裝與 Deref)、Typestate Pattern(編譯期狀態機)、 RAII Guard Pattern(資源管理)、Interior Mutability(Cell/RefCell/Mutex)、 Strategy Pattern(trait objects vs enum dispatch)、 Command Pattern、Observer Pattern(回呼與事件系統)、 Extension Trait Pattern、Sealed Trait Pattern、From/Into 慣用轉換。 觸發關鍵詞:Rust design pattern, builder, newtype, typestate, RAII, interior mutability, strategy pattern, trait object, enum dispatch, sealed trait, extension trait, From Into, guard pat

安装量
1
GitHub Stars
0
最近更新
3月29日