宏、unsafe 与 FFI advanced 30 分钟 更新 2026-06-15

FFI:与 C 互操作

用 extern "C" 调用 C 函数,并把 Rust 函数导出给 C。

FFI:与 C 互操作

你会学到什么

  • unsafe extern "C" 声明要调用的 C 函数。
  • 调用外部函数是 unsafe,通常包成安全接口。
  • #[unsafe(no_mangle)] extern "C" 把 Rust 函数导出给 C。

最小示例

unsafe extern "C" {
    fn abs(input: i32) -> i32;
}

运行代码

cd examples
cargo run -p rt_41_ffi
cargo test -p rt_41_ffi

代码讲解

extern "C" 指定 C 的调用约定(ABI)。edition 2024 要求 extern 块本身标注 unsafe,因为编译器无法验证外部声明是否正确:

fn safe_abs(value: i32) -> i32 {
    // SAFETY: abs 对普通 i32 有定义。
    unsafe { abs(value) }
}

反过来,把 Rust 函数暴露给 C 需要稳定的符号名和 C ABI:

#[unsafe(no_mangle)]
pub extern "C" fn rust_add(left: i32, right: i32) -> i32 {
    left + right
}

no_mangle 阻止编译器改写符号名(edition 2024 写成 #[unsafe(no_mangle)]),这样 C 代码就能按 rust_add 链接到它。

跨语言传递字符串、结构体时要特别小心内存布局和所有权;复杂场景常借助 bindgen / cbindgen 自动生成绑定。

常见错误

跨 FFI 边界传 Rust 的 String&str,它们不是 C 兼容布局。应使用 *const c_char + std::ffi::CString / CStr 转换。

练习

  • 声明并调用 C 的 sqrt(来自 libm/libc),封装成安全函数。
  • 导出一个 rust_mul 函数,写测试确认从 Rust 调用正确。

小结

FFI 通过 extern "C" 和稳定 ABI 打通 Rust 与 C。调用外部代码是 unsafe,导出要 no_mangle,字符串/结构体跨界需谨慎处理布局。

下一步

最后一站:用一个真实项目把所学综合起来。

完整示例代码

下面是 examples/41_ffi/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。

examples/41_ffi/src/main.rs
//! FFI:与 C 互操作。

// 从 C 标准库导入 abs。edition 2024 要求 extern 块标注 unsafe。
unsafe extern "C" {
    fn abs(input: i32) -> i32;
}

/// 安全封装:调用 C 函数是 unsafe,这里包成安全接口。
fn safe_abs(value: i32) -> i32 {
    // SAFETY: abs 对任意 i32 都有定义(i32::MIN 除外,这里不涉及)。
    unsafe { abs(value) }
}

/// 导出给 C 调用的 Rust 函数。edition 2024 用 #[unsafe(no_mangle)]。
#[unsafe(no_mangle)]
pub extern "C" fn rust_add(left: i32, right: i32) -> i32 {
    left + right
}

fn main() {
    println!("abs(-5) via C = {}", safe_abs(-5));
    println!("rust_add(2, 3) = {}", rust_add(2, 3));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calls_c_abs() {
        assert_eq!(safe_abs(-5), 5);
        assert_eq!(safe_abs(7), 7);
    }

    #[test]
    fn exported_fn_works_from_rust() {
        assert_eq!(rust_add(2, 3), 5);
    }
}
examples/41_ffi/Cargo.toml
[package]
name = "rt_41_ffi"
version.workspace = true
edition.workspace = true
publish.workspace = true