外部块
Syntax
ExternBlock →
unsafe?1 extern Abi? {
InnerAttribute*
ExternalItem*
}
ExternalItem →
OuterAttribute* (
MacroInvocationSemi
| Visibility? StaticItem
| Visibility? Function
)
外部块提供不在当前 crate 中定义的程序项的声明,是 Rust 外部函数接口的基础。这类似于未检查的导入。
调用在外部块中声明的 unsafe 函数或访问 unsafe 静态项只允许在 unsafe 上下文中进行。
外部块在其所在模块或块的值命名空间中定义其函数和静态项。
在语义上,unsafe 关键字必须出现在外部块的 extern 关键字之前。
2024 Edition differences
在 2024 版本之前,
unsafe关键字是可选的。只有当外部块本身被标记为unsafe时,才允许使用safe和unsafe程序项限定符。
函数
外部块中的函数声明方式与其他 Rust 函数相同,不同之处在于它们不能有主体,而是以分号结束。
参数中不允许使用模式,只能使用 IDENTIFIER 或 _。
允许使用 safe 和 unsafe 函数限定符,但不允许使用其他函数限定符(例如 const、async、extern)。
外部块中的函数可以被 Rust 代码调用,就像在 Rust 中定义的函数一样。Rust 编译器会自动在 Rust ABI 和外部 ABI 之间进行翻译。
在 extern 块中声明的函数隐式是 unsafe 的,除非存在 safe 函数限定符。
当强制转换为函数指针时,extern 块中声明的函数具有类型 for<'l1, ..., 'lm> extern "abi" fn(A1, ..., An) -> R,其中 'l1、…'lm 是其生命周期参数,A1、…、An 是其参数的声明类型,R 是声明的返回类型。
静态项
外部块中的静态项声明方式与外部块外的静态项相同,不同之处在于没有初始化其值的表达式。
除非在 extern 块中声明的静态项被限定为 safe,否则访问该程序项是 unsafe 的,无论其是否可变,因为无法保证该静态项内存中的位模式对于其声明的类型是有效的,因为某个任意的(例如 C)代码负责初始化该静态项。
extern 静态项可以像外部块外的静态项一样是不可变的或可变的。
不可变静态项必须在任何 Rust 代码执行之前被初始化。仅在该静态项在 Rust 代码读取它之前被初始化是不够的。一旦 Rust 代码运行,修改不可变静态项(从 Rust 内部或外部)是 UB,除非修改发生在 UnsafeCell 内部的字节上。
ABI
extern 关键字后可以跟一个可选的 ABI 字符串。ABI 指定块中函数的调用约定。调用约定定义了函数的低级接口,例如参数如何放置在寄存器或栈上、返回值如何传递以及谁负责清理栈。
Example
#![allow(unused)] fn main() { // Windows API 的接口。 unsafe extern "system" { /* ... */ } }
如果未指定 ABI 字符串,则默认为 "C"。
Note
不带显式 ABI 的
extern语法正被逐步淘汰,因此最好始终显式地写出 ABI。详情请参见 Rust issue #134986。
以下 ABI 字符串在所有平台上都受支持:
unsafe extern "Rust"— Rust 函数和闭包的本机调用约定。这是声明函数而不使用extern fn时的默认值。Rust ABI 不提供稳定性保证。
unsafe extern "C"— “C” ABI 与目标平台的占主导地位的 C 编译器选择的默认 ABI 相匹配。
-
unsafe extern "system"— 这等价于extern "C",但在 Windows x86_32 上等价于非可变参数函数的"stdcall",对于可变参数函数等价于"C"。Note
由于 Windows 上正确的底层 ABI 是目标平台特定的,因此在尝试链接不使用显式定义 ABI 的 Windows API 函数时,最好使用
extern "system"。
extern "C-unwind"和extern "system-unwind"— 分别与"C"和"system"相同,但在被调用方展开(通过 panic 或抛出 C++ 风格的异常)时具有不同的行为。
还有一些特定于平台的 ABI 字符串:
-
unsafe extern "cdecl"— 通常与 x86_32 C 代码一起使用的调用约定。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__cdecl以及 GCC 和 clang 的__attribute__((cdecl))。
-
unsafe extern "stdcall"— x86_32 上的 Win32 API 通常使用的调用约定。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__stdcall以及 GCC 和 clang 的__attribute__((stdcall))。
-
unsafe extern "win64"— Windows x64 ABI。- 仅在 x86_64 目标上可用。
- “win64” 与 Windows x86_64 目标上的 “C” ABI 相同。
- 对应于 GCC 和 clang 的
__attribute__((ms_abi))。
-
unsafe extern "sysv64"— System V ABI。- 仅在 x86_64 目标上可用。
- “sysv64” 与非 Windows x86_64 目标上的 “C” ABI 相同。
- 对应于 GCC 和 clang 的
__attribute__((sysv_abi))。
-
unsafe extern "aapcs"— ARM 的 soft-float ABI。- 仅在 ARM32 目标上可用。
- “aapcs” 与 soft-float ARM32 上的 “C” ABI 相同。
- 对应于 clang 的
__attribute__((pcs("aapcs")))。
Note
详情请参见:
-
unsafe extern "fastcall"— stdcall 的一种“快速“变体,通过寄存器传递某些参数。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__fastcall以及 GCC 和 clang 的__attribute__((fastcall))。
-
unsafe extern "thiscall"— x86_32 MSVC 上 C++ 类成员函数通常使用的调用约定。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__thiscall以及 GCC 和 clang 的__attribute__((thiscall))。
unsafe extern "efiapi"— 用于 UEFI 函数的 ABI。- 仅在 x86 和 ARM 目标(32 位和 64 位)上可用。
与 "C" 和 "system" 一样,大多数特定于平台的 ABI 字符串也有相应的 -unwind 变体;具体来说,这些是:
"aapcs-unwind""cdecl-unwind""fastcall-unwind""stdcall-unwind""sysv64-unwind""thiscall-unwind""win64-unwind"
可变参数函数
外部块中的函数可以通过将 ... 指定为最后一个参数来成为可变参数函数。可变参数可以有选择地指定一个标识符。
#![allow(unused)]
fn main() {
unsafe extern "C" {
unsafe fn foo(...);
unsafe fn bar(x: i32, ...);
unsafe fn with_name(format: *const u8, args: ...);
// 安全性:此函数保证不会访问
// 可变参数。
safe fn ignores_variadic_arguments(x: i32, ...);
}
}
Warning
不应在
extern块中的函数上使用safe限定符,除非该函数保证完全不会访问可变参数。向可变参数函数传递意外数量的参数或意外类型的参数可能导致未定义行为。
可变参数只能在具有以下 ABI 字符串或其相应 -unwind 变体的 extern 块中指定:
"aapcs""C""cdecl""efiapi""system""sysv64""win64"
外部块上的属性
以下属性控制外部块的行为。
link 属性
link 属性指定编译器应为 extern 块中的程序项与之链接的本地库的名称。
它使用 MetaListNameValueStr 语法来指定其输入。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")]
unsafe extern {
// …
}
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern {
// …
}
#[link(wasm_import_module = "foo")]
unsafe extern {
// …
}
在空外部块上添加 link 属性是有效的。你可以使用这种方式来满足代码中其他地方(包括上游 crate)的外部块的链接要求,而不是在每个外部块上都添加属性。
链接修饰符:bundle
此修饰符仅与 static 链接种类兼容。使用任何其他种类将导致编译器错误。
在构建 rlib 或 staticlib 时,+bundle 表示本地静态库将被打包到 rlib 或 staticlib 归档中,然后在最终二进制文件的链接过程中从那里取出。
在构建 rlib 时,-bundle 表示本地静态库按名称注册为该 rlib 的依赖项,其中的目标文件仅在最终二进制文件的链接过程中被包含,按名称文件搜索也在最终链接时执行。在构建 staticlib 时,-bundle 表示本地静态库根本不包含在归档中,需要某个更高级的构建系统在最终二进制文件的链接过程中稍后添加它。
此修饰符在构建其他目标(如可执行文件或动态库)时无效。
此修饰符的默认值为 +bundle。
关于此修饰符的更多实现细节可以在 bundle 的 rustc 文档中找到。
链接修饰符:whole-archive
此修饰符仅与 static 链接种类兼容。使用任何其他种类将导致编译器错误。
+whole-archive 表示将静态库作为完整归档链接,不丢弃任何目标文件。
此修饰符的默认值为 -whole-archive。
关于此修饰符的更多实现细节可以在 whole-archive 的 rustc 文档中找到。
链接修饰符:verbatim
此修饰符与所有链接种类兼容。
+verbatim 表示 rustc 本身不会向库名称添加任何目标平台指定的库前缀或后缀(如 lib 或 .a),并会尽量要求链接器也这样做。
-verbatim 表示 rustc 在将库名称传递给链接器之前会添加目标平台特定的前缀和后缀,或者不会阻止链接器隐式添加它们。
此修饰符的默认值为 -verbatim。
关于此修饰符的更多实现细节可以在 verbatim 的 rustc 文档中找到。
dylib 与 raw-dylib
在 Windows 上,链接动态库需要向链接器提供一个导入库:这是一个特殊的静态库,它声明了动态库导出的所有符号,以使得链接器知道它们必须在运行时动态加载。
指定 kind = "dylib" 指示 Rust 编译器根据 name 键链接一个导入库。然后链接器将使用其正常的库解析逻辑来查找该导入库。或者,指定 kind = "raw-dylib" 指示编译器在编译期间生成一个导入库并将其提供给链接器。
raw-dylib 仅在 Windows 上受支持。在面向其他平台时使用它将导致编译器错误。
import_name_type 键
在 x86 Windows 上,函数名称会被“修饰“(即添加特定的前缀和/或后缀)以指示其调用约定。例如,一个名为 fn1 的 stdcall 调用约定函数(没有参数)会被修饰为 _fn1@0。然而,PE 格式也允许名称不带前缀或不被修饰。此外,MSVC 和 GNU 工具链对相同的调用约定使用不同的修饰,这意味着默认情况下,某些 Win32 函数无法通过 GNU 工具链使用 raw-dylib 链接种类进行调用。
为了应对这些差异,在使用 raw-dylib 链接种类时,你还可以指定 import_name_type 键,其值可以是以下之一,用于更改生成的导入库中函数的命名方式:
decorated:函数名将使用 MSVC 工具链格式进行完全修饰。noprefix:函数名将使用 MSVC 工具链格式进行修饰,但跳过前导的?、@或可选的_。undecorated:函数名将不被修饰。
如果未指定 import_name_type 键,则函数名将使用目标工具链的格式进行完全修饰。
变量永远不会被修饰,因此 import_name_type 键对它们在生成的导入库中的命名方式没有影响。
import_name_type 键仅在 x86 Windows 上受支持。在面向其他平台时使用它将导致编译器错误。
link_name 属性
link_name 属性 可以应用于 extern 块中的声明,以指定为给定函数或静态项导入的符号。
Example
#![allow(unused)] fn main() { unsafe extern "C" { #[link_name = "actual_symbol_name"] safe fn name_in_rust(); } }
link_name 属性使用 MetaNameValueStr 语法。
link_name 属性只能应用于 extern 块中的函数或静态项。
Note
rustc会忽略在其他位置的使用但会给出 lint 警告。这将来可能变成错误。
只有程序项上首次使用的 link_name 才会生效。
Note
rustc会对首次之后的任何使用以未来兼容性警告的形式给出 lint 警告。这将来可能变成错误。
link_name 属性不能与 link_ordinal 属性一起使用。
link_ordinal 属性
link_ordinal 属性可以应用于 extern 块中的声明,以指示在生成导入库进行链接时要使用的数字序号。序号是 Windows 上动态库中每个导出符号的唯一编号,可以在加载库时使用,以查找该符号,而不必按名称查找。
Warning
link_ordinal应仅在已知符号的序号是稳定的情况下使用:如果符号的序号在构建其所在的二进制文件时未显式设置,则会自动分配一个序号,并且该分配的序号可能在二进制文件的构建之间发生变化。
#![allow(unused)]
fn main() {
#[cfg(all(windows, target_arch = "x86"))]
#[link(name = "exporter", kind = "raw-dylib")]
unsafe extern "stdcall" {
#[link_ordinal(15)]
safe fn imported_function_stdcall(i: i32);
}
}
此属性仅与 raw-dylib 链接种类一起使用。使用任何其他种类将导致编译器错误。
将此属性与 link_name 属性一起使用将导致编译器错误。
函数参数上的属性
extern 函数参数上的属性遵循与常规函数参数相同的规则和限制。
-
从 2024 版本开始,
unsafe关键字在语义上是必需的。 ↩