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…

Quelltext ansehen

Verwendung

  1. Installationsbefehl kopieren und das Quellpaket vor der Installation in der eigenen Agentenumgebung prüfen.
  2. Benötigte Werkzeuge, Zugangsdaten und Hosts im Original prüfen.
  3. Eingabe und gewünschtes Ergebnis beschreiben und die Ausgabe mit den Quellen abgleichen.
npx skills add https://github.com/luckyegg168/rust-skill --skill rust-ffi-interop

Vor dem Start

  • Die Installation umfasst weder Modellabo noch API-Kontingent oder Dateizugriffsrechte.
  • Sterne und Installationen zeigen Aktivität, keine unabhängige Qualitäts- oder Sicherheitsprüfung.
Originales Skill-Dokument

Aus dem Quell-Repository gerendert; Überschriften, Beispiele, Code, Tabellen, Links und Bilder bleiben erhalten.

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。

aus demselben Repository

Weitere Skills

Alle Skills
luckyegg168
Community

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

Installationen
1
GitHub Stars
0
Aktualisiert
29. März
luckyegg168
Community

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

Installationen
1
GitHub Stars
0
Aktualisiert
29. März
luckyegg168
Community

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

Installationen
1
GitHub Stars
0
Aktualisiert
29. März