Rust FFI 入门:extern、ABI 与底层符号链接解析

· 6min · Paxon Qiao

Rust FFI 入门:extern、ABI 与底层符号链接解析

Rust 以内存安全著称,但有时我们需要打破边界,与 C/C++ 等外部代码交互,或者直接进行底层操作,这就是 外部函数接口(FFI) 的用武之地。作为一篇入门指南,我们将通过一个精简的 Rust 代码示例,聚焦于 FFI 的三大基石:extern调用约定(ABI)符号链接。学习如何使用它们,是您迈向 Rust 高级系统编程的关键第一步。

Rust FFI 是连接底层世界的桥梁。本文从入门角度出发,详细解析 externABI 调用约定#[no_mangle] 符号导出#[link_name] 别名这四大核心概念,通过实战代码,帮助你掌握 Rust FFI 的底层符号链接机制。

实操:Rust 内部的符号链接机制

Rust FFI 核心代码

// Rust is highly capable of sharing FFI interfaces with C/C++ and other statically compiled
// languages, and it can even link within the code itself! It makes it through the extern
// block, just like the code below.
//
// The short string after the `extern` keyword indicates which ABI the externally imported
// function would follow. In this exercise, "Rust" is used, while other variants exists like
// "C" for standard C ABI, "stdcall" for the Windows ABI.
//
// The externally imported functions are declared in the extern blocks, with a semicolon to
// mark the end of signature instead of curly braces. Some attributes can be applied to those
// function declarations to modify the linking behavior, such as #[link_name = ".."] to
// modify the actual symbol names.
//
// If you want to export your symbol to the linking environment, the `extern` keyword can
// also be marked before a function definition with the same ABI string note. The default ABI
// for Rust functions is literally "Rust", so if you want to link against pure Rust functions,
// the whole extern term can be omitted.
//
// Rust mangles symbols by default, just like C++ does. To suppress this behavior and make
// those functions addressable by name, the attribute #[no_mangle] can be applied.

extern "Rust" {
    fn my_demo_function(a: u32) -> u32;
    #[link_name = "my_demo_function"]
    fn my_demo_function_alias(a: u32) -> u32;
}

mod Foo {
    // No `extern` equals `extern "Rust"`.
    #[no_mangle]
    fn my_demo_function(a: u32) -> u32 {
        a
    }
}

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

    #[test]
    fn test_success() {
        // The externally imported functions are UNSAFE by default
        // because of untrusted source of other languages. You may
        // wrap them in safe Rust APIs to ease the burden of callers.
        //
        // SAFETY: We know those functions are aliases of a safe
        // Rust function.
        unsafe {
            my_demo_function(123);
            my_demo_function_alias(456);
        }
    }
}

这段 Rust 代码详细展示了 外部函数接口(FFI) 在 Rust 内部的运用,主要围绕如何使用 extern 块进行函数声明和符号链接。

核心机制和作用:

这段代码通过使用 extern "Rust" { ... } 块来**导入(或声明)**函数签名,尽管这些函数的实际定义 (my_demo_function) 存在于代码的另一个模块 Foo 内部。这种模式通常用于与其他语言(如 C/C++)或动态库进行交互,但在本例中,它被用于演示 Rust 内部的符号链接机制。

  1. extern 块与 ABI: extern "Rust" { ... } 定义了一个外部块,其中 "Rust" 指定了调用约定(ABI)。这意味着被声明的函数将遵循 Rust 编译器默认的调用规则。其他常见的 ABI 包括 "C"(标准 C 语言 ABI)或 "stdcall"(Windows ABI)。
  2. 符号导出与 #[no_mangle]Foo 模块内部定义的函数 my_demo_function 前使用了 #[no_mangle] 属性。通常,Rust 会像 C++ 一样对函数名进行符号重整(Name Mangling),使其在二进制文件中难以直接通过名称查找。#[no_mangle] 阻止了这一行为,确保了函数在链接环境中有一个干净、可预测的名称 (my_demo_function)。
  3. 符号别名与 #[link_name]extern 块中,my_demo_function_alias 明确使用了 #[link_name = "my_demo_function"] 属性。这告诉链接器,尽管函数在 Rust 代码中被命名为 my_demo_function_alias,但它应该链接到外部环境中名为 my_demo_function 的实际符号上。
  4. 不安全调用: 任何在 extern 块中声明的函数,无论是链接到 C 代码还是像本例中链接到 Rust 代码,默认都被视为不安全(unsafe。这是因为编译器无法验证外部函数是否满足 Rust 的内存安全和并发安全规则。因此,在测试用例中,对 my_demo_function 及其别名的调用必须被包裹在 unsafe { ... } 块内,以此作为程序员对内存安全的承诺

总之,这段代码是理解 Rust FFI 和链接机制的基础示例,展示了如何使用 extern#[no_mangle]#[link_name] 来控制函数符号的导入和导出,从而实现跨语言或跨模块的低级别通信。

总结

这段代码是理解 Rust FFI 和符号链接机制的绝佳示例。它展示了三个核心要素:通过 extern 确定调用约定(ABI),通过 #[no_mangle] 导出清晰的函数符号,以及通过 #[link_name] 创建符号别名。

掌握这些属性,意味着您获得了在 Rust 安全框架下,与底层内存和外部代码进行高性能、低级别通信的能力。但请务必记住,任何 FFI 调用都是对内存安全的主动接管,必须严格履行安全契约。

参考