外部块

语法
外部块 :
   unsafe? extern Abi? {
      内部属性*
      外部条目*
   }

外部条目 :
   外围属性* (
         宏调用语句
      | ( 可见性? ( 静态条目 | 函数 ) )
   )

外部块提供了在当前 crate 中未定义的条目的 声明 ,是 Rust 实现外部函数接口 (FFI) 的基础。这类似于未经检查的导入。

在外部块中允许两种条目声明: 函数静态 。 仅在 unsafe 上下文中才允许调用外部块中声明的函数或访问静态。

unsafe 关键字在 extern 关键字之前语法上是被允许的,但是在语义层面上会被拒绝。 因而宏可以从语法上使用 unsafe 关键字,然后从令牌流中移除。

函数

在外部块中声明的函数与其他 Rust 函数的声明方式相同,但是声明不能有函数体,仅以分号结束。 参数中不允许使用模式,只能使用 标识符_ 。不允许使用函数修饰符 (如 constasyncunsafeextern )。

外部块中的函数可以像 rust 中定义的函数一样被 rust 代码调用。 Rust 编译器会自动在 Rust ABI 和外部 ABI 之间进行转换。

在外部块中声明的函数隐式地被认为是 unsafe 的。 当将其强转为函数指针时,外部块中声明的函数类型为 unsafe extern "abi" for<'l1, ..., 'lm> fn(A1, ..., An) -> R, 其中 'l1 , ... 'lm 为其生命周期参数, A1 , ... , An 为参数的声明类型,而 R 为返回值的声明类型。

Statics

在外部块中声明的静态的方式与 静态 在外部块之外声明的方式相同,但没有初始化表达式。 访问在外部块中声明的静态条目是非安全的,无论它是否可变,因为无法保证静态内存中的位模式是否有效,因为负责初始化该静态的代码是不确定的 (例如 C 语言) 。

就像 静态 在外部块之外声明一样,外部静态可以是不可变的也可以是可变的。 在执行任何 Rust 代码之前, 必须 先初始化不可变的静态。仅在 Rust 代码读取它之前,才将其初始化是不够的。

ABI

默认情况下,外部块假设它们正在调用的库在特定平台上使用标准 C ABI。 可以使用 abi 字符串指定其他 ABI,如下所示:

#![allow(unused)]
fn main() {
// 对于Windows API的接口
extern "stdcall" { }
}

有三个跨平台的 ABI 字符串,以确保所有编译器支持:

  • extern "Rust" -- 在 Rust 代码中编写普通 fn foo() 时的默认 ABI。
  • extern "C" -- 与 extern fn foo() 相同,使用你的 C 编译器支持的默认 ABI。
  • extern "system" -- 通常与 extern "C" 相同,但在 Win32 上是 "stdcall" ,在链接到 Windows API 本身时应使用它。

此外,还有一些特定于平台的 ABI 字符串:

  • extern "cdecl" -- x86_32 C 代码的默认 ABI。
  • extern "stdcall" -- x86_32 上 Win32 API 的默认 ABI。
  • extern "win64" -- x86_64 Windows 上 C 代码的默认 ABI。
  • extern "sysv64" -- 非 Windows x86_64 上 C 代码的默认 ABI。
  • extern "aapcs" -- ARM 的默认 ABI。
  • extern "fastcall" -- fastcall ABI,对应于 MSVC 的 __fastcall 和 GCC 和 clang 的 __attribute__((fastcall))
  • extern "vectorcall"-- vectorcall ABI,对应于 MSVC 的 __vectorcall 和 clang 的 __attribute__((vectorcall))
  • extern "efiapi" -- 用于 UEFI 函数的 ABI。

可变函数

在外部块中的函数可以通过将 ... 指定最后一个参数为变参。 在变参之前必须至少有一个参数。可以使用标识符选择性地指定变量参数。

#![allow(unused)]
fn main() {
extern "C" {
    fn foo(x: i32, ...);
    fn with_name(format: *const u8, args: ...);
}
}

外部块的属性

以下 属性 控制外部块的行为。

link 属性 指定编译器为 extern 块中的条目链接的本地库的名称。 它使用 元列表名称值字符串 语法来指定其输入。 name 键是要链接的本地库的名称。 kind 键是可选值,用于指定以下可能值的库类型:

  • dylib - 表示动态库。如果未指定 kind ,则为默认值。
  • static - 表示静态库。
  • framework - 表示 macOS 框架。仅适用于 macOS 目标。
  • raw-dylib - 表示动态库,其中编译器将生成要链接的导入库 (详情参见 dylib 对比 raw-dylib )。仅适用于 Windows 目标。

如果指定了 kind ,必须包括 name 键。

可选的 modifiers 参数是指定要链接的库的链接修饰符的一种方法。 修饰符以逗号分隔的字符串形式指定,每个修饰符前有 +- 前缀,以表明该修饰符已启用或已禁用。 目前不支持在单个 link 属性中指定多个 modifiers 参数,或在同一 modifiers 参数中指定多个相同的修饰符。 例如: #[link(name = "mylib", kind = "static", modifiers = "+whole-archive")]

当从主机环境导入符号时,可以使用 wasm_import_module 键来指定 extern 块中条目的 WebAssembly 模块 名称。 如果未指定 wasm_import_module ,则默认模块名为 env

#[link(name = "crypto")]
extern {
    // …
}

#[link(name = "CoreFoundation", kind = "framework")]
extern {
    // …
}

#[link(wasm_import_module = "foo")]
extern {
    // …
}

可以在空的外部块上添加 link 属性。 你可以使用它来满足代码中其他地方 (包括上游 crates) 的外部块的链接要求,而不是为每个外部块都添加属性。

链接修饰符: bundle

此修饰符仅与 static 链接类型兼容。使用任何其他类型都会导致编译器错误。

在构建 rlib 或 staticlib 时, +bundle 表示本地静态库将被打包到 rlib 或 staticlib 归档中,然后在链接最终二进制文件时从其中检索出来。

在构建 rlib 时, -bundle 表示本地静态库以名称的方式注册为该 rlib 的依赖项,并且其中的目标文件仅在链接最终二进制文件时包含,文件搜索也在最终链接期间按该名称执行。
在构建 staticlib 时, -bundle 表示本地静态库不会被包含在档案中,某些更高级别的构建系统需要在链接最终二进制文件时随后添加它。

当构建其他目标 (如可执行文件或动态库) 时,此修饰符不起作用。

修饰符的默认值是 +bundle

关于这个修饰符的更多实现细节可在 rustc 文档 bundle 找到 。

链接修饰符: whole-archive

此修饰符仅与 static 链接类型兼容。使用任何其他类型都会导致编译器错误。

+whole-archive 表示静态库作为整个归档进行链接,而不会丢弃任何对象文件。

这个修饰符的默认值是 -whole-archive

关于这个修改器的更多实现细节可在 rustc 文档 whole-archive 中找到。

链接修饰符: verbatim

此修饰符与所有链接类型兼容。

+verbatim 表示 rustc 本身不会为库名称添加任何目标指定的库前缀或后缀 (如 lib.a ) ,并尝试尽可能要求链接器也是如此。

-verbatim 表示 rustc 将在将库名称传递给链接器之前添加特定目标的前缀和后缀,且不会阻止链接器隐式添加它。

这个修饰符的默认值是 -verbatim

关于这个修饰符的更多实现细节可在 rustc 文档 verbatim 中找到。

dylib 对比 raw-dylib

在 Windows 上,链接动态库需要向链接器提供导入库:这是一个特殊的静态库,以这样一种方式声明动态库导出的所有符号,使得链接器知道它们必须在运行时动态加载。

指定 kind = "dylib" 会指示 Rust 编译器基于 name 键链接导入库。 然后,链接器将使用其正常的库解析逻辑来找到该导入库。另外,指定 kind = "raw-dylib" 会指示编译器在编译期间生成一个导入库,并提供给链接器。

只有在 Windows 上才支持 raw-dylib ,不支持 32 位 x86 ( target_arch="x86" ) 。将其用于针对其他平台或 Windows 上的 x86 时将导致编译器错误。

extern 块内的声明上可以指定 link_name 属性 ,以指示要为给定函数或静态导入的符号。 使用 元名称值字符串 语法来指定符号的名称。

#![allow(unused)]
fn main() {
extern {
    #[link_name = "actual_symbol_name"]
    fn name_in_rust();
}
}

将此属性与 link_ordinal 属性一起使用会导致编译器错误。

可以在 extern 块内的声明上应用 link_ordinal 属性,以指示生成要链接的导入库时要使用的数字序数。 在 Windows 上,每个动态库导出的符号都有一个唯一的编号,可以在加载库时使用该编号查找该符号,而不必通过名称查找它。

警告:link_ordinal 只应在符号的序数已知为稳定时使用:如果没有在构建其所在二进制文件时显式设置符号的序数,则将自动分配一个序数,而该分配的序数可能会在二进制文件的不同构建之间发生变化。

#[link(name = "exporter", kind = "raw-dylib")]
extern "stdcall" {
    #[link_ordinal(15)]
    fn imported_function_stdcall(i: i32);
}

此属性仅与 raw-dylib 链接类型一起使用。使用任何其他类型都会导致编译器错误。

将此属性与 link_name 属性一起使用将导致编译器错误。

函数参数的属性

外部函数参数的属性遵循与 常规函数参数 相同的规则和限制。