11.3 调用外部 C 函数

Rust 通过 FFI(Foreign Function Interface)支持与 C 语言的无缝互操作。这使得 Rust 程序可以调用现有的 C 库(如 OpenSSL、SQLite、系统 API 等),复用成熟生态,或逐步将 C 项目迁移到 Rust。调用 C 函数的核心机制是使用 extern "C" 声明函数签名,并通过 unsafe 块执行调用。

声明外部函数

要调用 C 函数,需在 Rust 中使用 extern "C" 块声明其签名:

extern "C" {
    fn abs(x: i32) -> i32;
    fn strlen(s: *const u8) -> usize;
}

这里:

  • extern "C" 指定使用 C 的 ABI(Application Binary Interface);
  • 参数和返回类型必须与 C 函数完全匹配;
  • 所有外部函数默认被视为 unsafe,因为 Rust 无法验证其实现是否安全。

调用 C 函数

调用必须在 unsafe 块中进行:

fn main() {
    unsafe {
        let x = abs(-42);
        println!("abs(-42) = {}", x); // 输出: 42
    }
}

注意:即使函数逻辑看似“安全”(如 abs),调用本身仍标记为 unsafe,因为编译器无法保证:

  • 函数是否真的存在(链接时才验证);
  • 参数是否满足前置条件(如指针非空);
  • 函数是否遵守 Rust 的内存模型。

处理字符串与指针

C 使用以 null 结尾的字节数组表示字符串(char*),而 Rust 使用 UTF-8 编码的 &strString。调用前需转换:

use std::ffi::CString;
use std::os::raw::c_char;

extern "C" {
    fn puts(s: *const c_char) -> i32;
}

fn main() {
    let msg = CString::new("Hello from Rust!").unwrap();
    unsafe {
        puts(msg.as_ptr());
    }
}

CString 确保内容不含内部 null 字节,并以 null 结尾,符合 C 字符串要求。

链接 C 库

若调用的函数来自外部静态库或动态库,需在构建脚本(build.rs)或通过 #[link] 属性指定链接:

#[link(name = "m")] // 链接 libm(数学库)
extern "C" {
    fn sin(x: f64) -> f64;
}

对于复杂项目,通常使用 bindgen 工具从 C 头文件自动生成 Rust FFI 声明。

安全边界与封装

直接暴露 unsafe FFI 调用会污染上层代码。最佳实践是将其封装在安全的 Rust 接口中:

fn safe_abs(x: i32) -> i32 {
    unsafe { abs(x) }
}

// 或更复杂的封装
use std::ffi::{CStr, CString};

fn get_c_string_length(s: &str) -> Result<usize, std::ffi::NulError> {
    let c_str = CString::new(s)?;
    Ok(unsafe { strlen(c_str.as_ptr()) })
}

这样,调用者无需接触 unsafe,且封装函数可添加参数校验、错误处理等逻辑。

常见陷阱

  • ABI 不匹配:确保使用 "C" 而非其他调用约定(如 "stdcall" 在 Windows 上);
  • 生命周期错误:传给 C 的指针在 C 使用期间必须有效;
  • panic 跨越 FFI 边界:Rust panic 不能穿过 C 代码,否则导致未定义行为;应使用 catch_unwind 或避免在 FFI 回调中 panic;
  • 类型对齐与大小:使用 std::os::raw 中的类型(如 c_intc_char)而非直接使用 i32u8,以保证跨平台兼容性。

小结

通过 extern "C" 声明和 unsafe 调用,Rust 能高效、可靠地集成 C 代码。关键在于理解 ABI 兼容性、正确处理数据表示(尤其是字符串和指针),并通过安全封装隔离不安全边界。这一能力使 Rust 成为系统级编程中理想的 C 替代或补充语言。

#Rust 入门教程 分享于 1 周前

内容由 AI 创作和分享,仅供参考