このページの目次

C ABI と bindgen

Rust から C を呼び出す第一步は、型レイアウトを一致させることです。repr(C) は Rust の struct が C ABI に従ってフィールドを配置し、C ヘッダ内の構造体とバイト単位でアラインメントされることを強制します。bindgen は C ヘッダから対応する Rust 型と関数シグネチャを自動生成し、手動で Rust/C の型マッピングを維持する退屈さとエラーを自動化します。callback の安全性の核心は、C から Rust へのコールバック時に panic が FFI 境界を越えて unwind してはならないという点にあります。

C ↔ Rust 型マッピング

FFI における型の対応は自動的ではありません。std::os::raw または libc クレート内の C 互換型を明示的に使用する必要があります。

C 型Rust 型 (libc)注意
intc_int (i32)
longc_longWindows では 32-bit、Linux では 64-bit!
size_tusize
char * (文字列)*const c_charCStr::from_ptr()
void **mut c_void
構造体ポインタ (opaque)*mut MyStruct間接参照せず、渡すのみ
enum#[repr(C)] enumC の enum は c_int サイズ

重要な罠:long のサイズは LLP64 (Windows) では 4 バイト、LP64 (Linux) では 8 バイトです。C コードが long を 8 バイトとして依存している場合、Windows では i64 を代わりに使用してください。

bindgen による自動化

bindgen は C ヘッダから Rust の外部関数宣言、構造体、定数を自動生成します。

// build.rs
fn main() {
    println!("cargo:rerun-if-changed=wrapper.h");

    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .allowlist_function("my_lib_.*")     // これらの関数のみバインド
        .allowlist_type("MyStruct")          // これらの型のみのバインド
        .generate()
        .expect("Unable to generate bindings");

    bindings.write_to_file(
        std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
            .join("bindings.rs"),
    ).expect("Couldn't write bindings");
}

// src/lib.rs
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

bindgen が処理すること:基本型のマッピング、struct のアラインメント、ネストされた struct、関数ポインタ、条件付き定義。 処理できないこと:インライン関数 (Rust へ手動でポートが必要)、C++ テンプレート。

Callback: C から Rust への安全な境界

extern "C" fn callback(x: i32) {
    println!("C called with {}", x);
}

unsafe { register_callback(callback as unsafe extern "C" fn(i32)); }

callback 内で panic が FFI 境界を越えて発生すると未定義動作 (UB) になります。Cargo.toml で panic = "abort" を設定することは、このリスクを回避する最も直接的な方法です。unwinding を維持する必要がある場合は、callback 内のすべてのロジックを catch_unwind でラップしてください。

参考

  • bindgen: rust-lang.github.io/rust-bindgen
  • Rustonomicon: FFI
  • libc crate: docs.rs/libc

Keywords: C ABI, bindgen, c_int, c_long, c_void, callback, panic across FFI, #[repr(C)]